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

Commit e2b61a54 authored by Desh's avatar Desh
Browse files

Use MetadataSyncAdapter in AppFunctionManagerService.

Flag: android.app.appfunctions.flags.enable_app_function_manager
Test: atest FrameworksAppFunctionsTests -c
Bug: 357551503
Change-Id: Iec9ddab67f40757c72cbf4257f36081189ae40c5
parent 43db9d0d
Loading
Loading
Loading
Loading
+53 −0
Original line number Diff line number Diff line
@@ -16,7 +16,15 @@

package com.android.server.appfunctions;

import android.annotation.NonNull;
import android.os.UserHandle;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -33,5 +41,50 @@ public final class AppFunctionExecutors {
                    /* unit= */ TimeUnit.SECONDS,
                    /* workQueue= */ new LinkedBlockingQueue<>());

    /** A map of per-user executors for queued work. */
    @GuardedBy("sLock")
    private static final SparseArray<ExecutorService> mPerUserExecutorsLocked = new SparseArray<>();

    private static final Object sLock = new Object();

    /**
     * Returns a per-user executor for queued metadata sync request.
     *
     * <p>The work submitted to these executor (Sync request) needs to be synchronous per user hence
     * the use of a single thread.
     *
     * <p>Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code
     * MetadataSyncAdapter}.
     */
    // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself.
    public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) {
        synchronized (sLock) {
            ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null);
            if (executor == null) {
                executor = Executors.newSingleThreadExecutor();
                mPerUserExecutorsLocked.put(user.getIdentifier(), executor);
            }
            return executor;
        }
    }

    /**
     * Shuts down and removes the per-user executor for queued work.
     *
     * <p>This should be called when the user is removed.
     */
    public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user)
            throws InterruptedException {
        ExecutorService executor;
        synchronized (sLock) {
            executor = mPerUserExecutorsLocked.get(user.getIdentifier());
            mPerUserExecutorsLocked.remove(user.getIdentifier());
        }
        if (executor != null) {
            executor.shutdown();
            var unused = executor.awaitTermination(30, TimeUnit.SECONDS);
        }
    }

    private AppFunctionExecutors() {}
}
+11 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.appfunctions;

import android.annotation.NonNull;
import android.app.appfunctions.AppFunctionManagerConfiguration;
import android.content.Context;

@@ -36,4 +37,14 @@ public class AppFunctionManagerService extends SystemService {
            publishBinderService(Context.APP_FUNCTION_SERVICE, mServiceImpl);
        }
    }

    @Override
    public void onUserUnlocked(@NonNull TargetUser user) {
        mServiceImpl.onUserUnlocked(user);
    }

    @Override
    public void onUserStopping(@NonNull TargetUser user) {
        mServiceImpl.onUserStopping(user);
    }
}
+183 −52
Original line number Diff line number Diff line
@@ -19,29 +19,35 @@ package com.android.server.appfunctions;
import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appfunctions.AppFunctionStaticMetadataHelper;
import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
import android.app.appfunctions.ExecuteAppFunctionResponse;
import android.app.appfunctions.IAppFunctionManager;
import android.app.appfunctions.IAppFunctionService;
import android.app.appfunctions.IExecuteAppFunctionCallback;
import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
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 android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Slog;
import android.app.appsearch.AppSearchResult;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService.TargetUser;
import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback;
import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener;

import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/** Implementation of the AppFunctionManagerService. */
public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
@@ -51,9 +57,11 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
    private final CallerValidator mCallerValidator;
    private final ServiceHelper mInternalServiceHelper;
    private final ServiceConfig mServiceConfig;
    private final Context mContext;

    public AppFunctionManagerServiceImpl(@NonNull Context context) {
        this(
                context,
                new RemoteServiceCallerImpl<>(
                        context, IAppFunctionService.Stub::asInterface, THREAD_POOL_EXECUTOR),
                new CallerValidatorImpl(context),
@@ -63,10 +71,12 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {

    @VisibleForTesting
    AppFunctionManagerServiceImpl(
            Context context,
            RemoteServiceCaller<IAppFunctionService> remoteServiceCaller,
            CallerValidator callerValidator,
            ServiceHelper appFunctionInternalServiceHelper,
            ServiceConfig serviceConfig) {
        mContext = Objects.requireNonNull(context);
        mRemoteServiceCaller = Objects.requireNonNull(remoteServiceCaller);
        mCallerValidator = Objects.requireNonNull(callerValidator);
        mInternalServiceHelper = Objects.requireNonNull(appFunctionInternalServiceHelper);
@@ -90,6 +100,26 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
        }
    }

    /** Called when the user is unlocked. */
    public void onUserUnlocked(TargetUser user) {
        Objects.requireNonNull(user);

        registerAppSearchObserver(user);
        trySyncRuntimeMetadata(user);
    }

    /** Called when the user is stopping. */
    public void onUserStopping(@NonNull TargetUser user) {
        Objects.requireNonNull(user);

        try {
            AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle());
            MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
        } catch (InterruptedException e) {
            Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e);
        }
    }

    private void executeAppFunctionInternal(
            ExecuteAppFunctionAidlRequest requestInternal,
            SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
@@ -132,7 +162,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
            return;
        }

        var unused = mCallerValidator
        var unused =
                mCallerValidator
                        .verifyCallerCanExecuteAppFunction(
                                validatedCallingPackage,
                                targetPackageName,
@@ -143,8 +174,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
                                        safeExecuteAppFunctionCallback.onResult(
                                                ExecuteAppFunctionResponse.newFailure(
                                                        ExecuteAppFunctionResponse.RESULT_DENIED,
                                                "Caller does not have permission to execute the"
                                                        + " appfunction",
                                                        "Caller does not have permission to execute"
                                                                + " the appfunction",
                                                        /* extras= */ null));
                                        return;
                                    }
@@ -154,7 +185,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
                                    if (serviceIntent == null) {
                                        safeExecuteAppFunctionCallback.onResult(
                                                ExecuteAppFunctionResponse.newFailure(
                                                ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
                                                        ExecuteAppFunctionResponse
                                                                .RESULT_INTERNAL_ERROR,
                                                        "Cannot find the target service.",
                                                        /* extras= */ null));
                                        return;
@@ -291,4 +323,103 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
        }
        return ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR;
    }

    private void registerAppSearchObserver(@NonNull TargetUser user) {
        AppSearchManager perUserAppSearchManager =
                mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0)
                        .getSystemService(AppSearchManager.class);
        if (perUserAppSearchManager == null) {
            Slog.d(TAG, "AppSearch Manager not found for user: " + user.getUserIdentifier());
            return;
        }
        try (FutureGlobalSearchSession futureGlobalSearchSession =
                new FutureGlobalSearchSession(
                        perUserAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR)) {
            AppFunctionMetadataObserver appFunctionMetadataObserver =
                    new AppFunctionMetadataObserver(
                            user.getUserHandle(),
                            mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0));
            var unused =
                    futureGlobalSearchSession
                            .registerObserverCallbackAsync(
                                    "android",
                                    new ObserverSpec.Builder().build(),
                                    THREAD_POOL_EXECUTOR,
                                    appFunctionMetadataObserver)
                            .whenComplete(
                                    (voidResult, ex) -> {
                                        if (ex != null) {
                                            Slog.e(TAG, "Failed to register observer: ", ex);
                                        }
                                    });

        } catch (IOException ex) {
            Slog.e(TAG, "Failed to close observer session: ", ex);
        }
    }

    private void trySyncRuntimeMetadata(@NonNull TargetUser user) {
        MetadataSyncAdapter metadataSyncAdapter =
                MetadataSyncPerUser.getPerUserMetadataSyncAdapter(
                        user.getUserHandle(),
                        mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0));
        if (metadataSyncAdapter != null) {
            var unused =
                    metadataSyncAdapter
                            .submitSyncRequest()
                            .whenComplete(
                                    (isSuccess, ex) -> {
                                        if (ex != null || !isSuccess) {
                                            Slog.e(TAG, "Sync was not successful");
                                        }
                                    });
        }
    }

    private static class AppFunctionMetadataObserver implements ObserverCallback {
        @Nullable private final MetadataSyncAdapter mPerUserMetadataSyncAdapter;

        AppFunctionMetadataObserver(@NonNull UserHandle userHandle, @NonNull Context userContext) {
            mPerUserMetadataSyncAdapter =
                    MetadataSyncPerUser.getPerUserMetadataSyncAdapter(userHandle, userContext);
        }

        @Override
        public void onDocumentChanged(@NonNull DocumentChangeInfo documentChangeInfo) {
            if (mPerUserMetadataSyncAdapter == null) {
                return;
            }
            if (documentChangeInfo
                            .getDatabaseName()
                            .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)
                    && documentChangeInfo
                            .getNamespace()
                            .equals(
                                    AppFunctionStaticMetadataHelper
                                            .APP_FUNCTION_STATIC_NAMESPACE)) {
                var unused = mPerUserMetadataSyncAdapter.submitSyncRequest();
            }
        }

        @Override
        public void onSchemaChanged(@NonNull SchemaChangeInfo schemaChangeInfo) {
            if (mPerUserMetadataSyncAdapter == null) {
                return;
            }
            if (schemaChangeInfo
                    .getDatabaseName()
                    .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)) {
                boolean shouldInitiateSync = false;
                for (String schemaName : schemaChangeInfo.getChangedSchemaNames()) {
                    if (schemaName.startsWith(AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE)) {
                        shouldInitiateSync = true;
                        break;
                    }
                }
                if (shouldInitiateSync) {
                    var unused = mPerUserMetadataSyncAdapter.submitSyncRequest();
                }
            }
        }
    }
}
+47 −21
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import android.annotation.WorkerThread;
import android.app.appfunctions.AppFunctionRuntimeMetadata;
import android.app.appfunctions.AppFunctionStaticMetadataHelper;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchManager.SearchContext;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.PackageIdentifier;
@@ -61,9 +63,8 @@ import java.util.concurrent.Executor;
 */
public class MetadataSyncAdapter {
    private static final String TAG = MetadataSyncAdapter.class.getSimpleName();
    private final FutureAppSearchSession mRuntimeMetadataSearchSession;
    private final FutureAppSearchSession mStaticMetadataSearchSession;
    private final Executor mSyncExecutor;
    private final AppSearchManager mAppSearchManager;
    private final PackageManager mPackageManager;

    // Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
@@ -73,13 +74,11 @@ public class MetadataSyncAdapter {

    public MetadataSyncAdapter(
            @NonNull Executor syncExecutor,
            @NonNull FutureAppSearchSession runtimeMetadataSearchSession,
            @NonNull FutureAppSearchSession staticMetadataSearchSession,
            @NonNull PackageManager packageManager) {
            @NonNull PackageManager packageManager,
            @NonNull AppSearchManager appSearchManager) {
        mSyncExecutor = Objects.requireNonNull(syncExecutor);
        mRuntimeMetadataSearchSession = Objects.requireNonNull(runtimeMetadataSearchSession);
        mStaticMetadataSearchSession = Objects.requireNonNull(staticMetadataSearchSession);
        mPackageManager = Objects.requireNonNull(packageManager);
        mAppSearchManager = Objects.requireNonNull(appSearchManager);
    }

    /**
@@ -89,31 +88,54 @@ public class MetadataSyncAdapter {
     *     synchronization was successful.
     */
    public AndroidFuture<Boolean> submitSyncRequest() {
        SearchContext staticMetadataSearchContext =
                new SearchContext.Builder(
                                AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)
                        .build();
        SearchContext runtimeMetadataSearchContext =
                new SearchContext.Builder(
                                AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB)
                        .build();
        AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>();
        mSyncExecutor.execute(
                () -> {
                    try {
                        trySyncAppFunctionMetadataBlocking();
                    try (FutureAppSearchSession staticMetadataSearchSession =
                                    new FutureAppSearchSessionImpl(
                                            mAppSearchManager,
                                            AppFunctionExecutors.THREAD_POOL_EXECUTOR,
                                            staticMetadataSearchContext);
                            FutureAppSearchSession runtimeMetadataSearchSession =
                                    new FutureAppSearchSessionImpl(
                                            mAppSearchManager,
                                            AppFunctionExecutors.THREAD_POOL_EXECUTOR,
                                            runtimeMetadataSearchContext)) {

                        trySyncAppFunctionMetadataBlocking(
                                staticMetadataSearchSession, runtimeMetadataSearchSession);
                        settableSyncStatus.complete(true);
                    } catch (Exception e) {
                        settableSyncStatus.completeExceptionally(e);

                    } catch (Exception ex) {
                        settableSyncStatus.completeExceptionally(ex);
                    }
                });
        return settableSyncStatus;
    }

    @WorkerThread
    private void trySyncAppFunctionMetadataBlocking()
    @VisibleForTesting
    void trySyncAppFunctionMetadataBlocking(
            @NonNull FutureAppSearchSession staticMetadataSearchSession,
            @NonNull FutureAppSearchSession runtimeMetadataSearchSession)
            throws ExecutionException, InterruptedException {
        ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap =
                getPackageToFunctionIdMap(
                        mStaticMetadataSearchSession,
                        staticMetadataSearchSession,
                        AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE,
                        AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID,
                        AppFunctionStaticMetadataHelper.PROPERTY_PACKAGE_NAME);
        ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap =
                getPackageToFunctionIdMap(
                        mRuntimeMetadataSearchSession,
                        runtimeMetadataSearchSession,
                        RUNTIME_SCHEMA_TYPE,
                        AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID,
                        AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME);
@@ -134,7 +156,7 @@ public class MetadataSyncAdapter {
            RemoveByDocumentIdRequest removeByDocumentIdRequest =
                    buildRemoveRuntimeMetadataRequest(removedFunctionsDiffMap);
            AppSearchBatchResult<String, Void> removeDocumentBatchResult =
                    mRuntimeMetadataSearchSession.remove(removeByDocumentIdRequest).get();
                    runtimeMetadataSearchSession.remove(removeByDocumentIdRequest).get();
            if (!removeDocumentBatchResult.isSuccess()) {
                throw convertFailedAppSearchResultToException(
                        removeDocumentBatchResult.getFailures().values());
@@ -144,13 +166,14 @@ public class MetadataSyncAdapter {
        if (!addedFunctionsDiffMap.isEmpty()) {
            // TODO(b/357551503): only set schema on package diff
            SetSchemaRequest addSetSchemaRequest =
                    buildSetSchemaRequestForRuntimeMetadataSchemas(appRuntimeMetadataSchemas);
                    buildSetSchemaRequestForRuntimeMetadataSchemas(
                            mPackageManager, appRuntimeMetadataSchemas);
            Objects.requireNonNull(
                    mRuntimeMetadataSearchSession.setSchema(addSetSchemaRequest).get());
                    runtimeMetadataSearchSession.setSchema(addSetSchemaRequest).get());
            PutDocumentsRequest putDocumentsRequest =
                    buildPutRuntimeMetadataRequest(addedFunctionsDiffMap);
            AppSearchBatchResult<String, Void> putDocumentBatchResult =
                    mRuntimeMetadataSearchSession.put(putDocumentsRequest).get();
                    runtimeMetadataSearchSession.put(putDocumentsRequest).get();
            if (!putDocumentBatchResult.isSuccess()) {
                throw convertFailedAppSearchResultToException(
                        putDocumentBatchResult.getFailures().values());
@@ -211,6 +234,7 @@ public class MetadataSyncAdapter {

    @NonNull
    private SetSchemaRequest buildSetSchemaRequestForRuntimeMetadataSchemas(
            @NonNull PackageManager packageManager,
            @NonNull Set<AppSearchSchema> metadataSchemaSet) {
        Objects.requireNonNull(metadataSchemaSet);
        SetSchemaRequest.Builder setSchemaRequestBuilder =
@@ -220,7 +244,7 @@ public class MetadataSyncAdapter {
            String packageName =
                    AppFunctionRuntimeMetadata.getPackageNameFromSchema(
                            runtimeMetadataSchema.getSchemaType());
            byte[] packageCert = getCertificate(packageName);
            byte[] packageCert = getCertificate(packageManager, packageName);
            if (packageCert == null) {
                continue;
            }
@@ -399,13 +423,15 @@ public class MetadataSyncAdapter {

    /** Gets the SHA-256 certificate from a {@link PackageManager}, or null if it is not found. */
    @Nullable
    private byte[] getCertificate(@NonNull String packageName) {
    private byte[] getCertificate(
            @NonNull PackageManager packageManager, @NonNull String packageName) {
        Objects.requireNonNull(packageManager);
        Objects.requireNonNull(packageName);
        PackageInfo packageInfo;
        try {
            packageInfo =
                    Objects.requireNonNull(
                            mPackageManager.getPackageInfo(
                            packageManager.getPackageInfo(
                                    packageName,
                                    PackageManager.GET_META_DATA
                                            | PackageManager.GET_SIGNING_CERTIFICATES));
+80 −0
Original line number Diff line number Diff line
/*
 * 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.Nullable;
import android.app.appsearch.AppSearchManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;

/** A Singleton class that manages per-user metadata sync adapters. */
public final class MetadataSyncPerUser {
    private static final String TAG = MetadataSyncPerUser.class.getSimpleName();

    /** A map of per-user adapter for synchronizing appFunction metadata. */
    @GuardedBy("sLock")
    private static final SparseArray<MetadataSyncAdapter> sPerUserMetadataSyncAdapter =
            new SparseArray<>();

    private static final Object sLock = new Object();

    /**
     * Returns the per-user metadata sync adapter for the given user.
     *
     * @param user The user for which to get the metadata sync adapter.
     * @param userContext The user context for the given user.
     * @return The metadata sync adapter for the given user.
     */
    @Nullable
    public static MetadataSyncAdapter getPerUserMetadataSyncAdapter(
            UserHandle user, Context userContext) {
        synchronized (sLock) {
            MetadataSyncAdapter metadataSyncAdapter =
                    sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null);
            if (metadataSyncAdapter == null) {
                AppSearchManager perUserAppSearchManager =
                        userContext.getSystemService(AppSearchManager.class);
                PackageManager perUserPackageManager = userContext.getPackageManager();
                if (perUserAppSearchManager != null) {
                    metadataSyncAdapter =
                            new MetadataSyncAdapter(
                                    AppFunctionExecutors.getPerUserSyncExecutor(user),
                                    perUserPackageManager,
                                    perUserAppSearchManager);
                    sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter);
                    return metadataSyncAdapter;
                }
            }
            return metadataSyncAdapter;
        }
    }

    /**
     * Removes the per-user metadata sync adapter for the given user.
     *
     * @param user The user for which to remove the metadata sync adapter.
     */
    public static void removeUserSyncAdapter(UserHandle user) {
        synchronized (sLock) {
            sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
        }
    }
}
Loading