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

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

Merge "Use MetadataSyncAdapter in AppFunctionManagerService." into main

parents 7acbb345 e2b61a54
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