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

Commit 176c12e0 authored by Oluwarotimi Adesina's avatar Oluwarotimi Adesina Committed by Android (Google) Code Review
Browse files

Merge "implementing AFMS.executeAppFunction" into main

parents 466c8e59 3cc31bfc
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import android.content.Context;
 *
 * <p>App function is a specific piece of functionality that an app offers to the system. These
 * functionalities can be integrated into various system features.
 *
 */
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
@SystemService(Context.APP_FUNCTION_SERVICE)
+13 −0
Original line number Diff line number Diff line
@@ -16,9 +16,22 @@

package android.app.appfunctions;

import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
import android.app.appfunctions.IExecuteAppFunctionCallback;

/**
* Interface between an app and the server implementation service (AppFunctionManagerService).
* @hide
*/
oneway interface IAppFunctionManager {
    /**
    * Executes an app function provided by {@link AppFunctionService} through the system.
    *
    * @param request the request to execute an app function.
    * @param callback the callback to report the result.
    */
    void executeAppFunction(
        in ExecuteAppFunctionAidlRequest request,
        in IExecuteAppFunctionCallback callback
    );
}
 No newline at end of file
+3 −6
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.server.appfunctions;

import static android.app.appfunctions.flags.Flags.enableAppFunctionManager;

import android.app.appfunctions.IAppFunctionManager;
import android.content.Context;

import com.android.server.SystemService;
@@ -27,19 +26,17 @@ import com.android.server.SystemService;
 * Service that manages app functions.
 */
public class AppFunctionManagerService extends SystemService {
    private final AppFunctionManagerServiceImpl mServiceImpl;

    public AppFunctionManagerService(Context context) {
        super(context);
        mServiceImpl = new AppFunctionManagerServiceImpl(context);
    }

    @Override
    public void onStart() {
        if (enableAppFunctionManager()) {
            publishBinderService(Context.APP_FUNCTION_SERVICE, new AppFunctionManagerStub());
            publishBinderService(Context.APP_FUNCTION_SERVICE, mServiceImpl);
        }
    }

    private static class AppFunctionManagerStub extends IAppFunctionManager.Stub {

    }
}
+196 −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.NonNull;
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.appfunctions.ServiceCallHelper;
import android.app.appfunctions.ServiceCallHelper.RunServiceCallCallback;
import android.app.appfunctions.ServiceCallHelper.ServiceUsageCompleteListener;
import android.app.appfunctions.ServiceCallHelperImpl;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

import java.util.Objects;
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 {
    private static final String TAG = AppFunctionManagerServiceImpl.class.getSimpleName();
    private final ServiceCallHelper<IAppFunctionService> mExternalServiceCallHelper;
    private final CallerValidator mCallerValidator;
    private final ServiceHelper mInternalServiceHelper;

    public AppFunctionManagerServiceImpl(@NonNull Context context) {
        this(new ServiceCallHelperImpl<>(
                        context,
                        IAppFunctionService.Stub::asInterface, new ThreadPoolExecutor(
                        /*corePoolSize=*/ Runtime.getRuntime().availableProcessors(),
                        /*maxConcurrency=*/ Runtime.getRuntime().availableProcessors(),
                        /*keepAliveTime=*/ 0L,
                        /*unit=*/ TimeUnit.SECONDS,
                        /*workQueue=*/ new LinkedBlockingQueue<>())),
                new CallerValidatorImpl(context),
                new ServiceHelperImpl(context));
    }

    @VisibleForTesting
    AppFunctionManagerServiceImpl(ServiceCallHelper<IAppFunctionService> serviceCallHelper,
                                  CallerValidator apiValidator,
                                  ServiceHelper appFunctionInternalServiceHelper) {
        mExternalServiceCallHelper = Objects.requireNonNull(serviceCallHelper);
        mCallerValidator = Objects.requireNonNull(apiValidator);
        mInternalServiceHelper =
                Objects.requireNonNull(appFunctionInternalServiceHelper);
    }

    @Override
    public void executeAppFunction(
            @NonNull ExecuteAppFunctionAidlRequest requestInternal,
            @NonNull IExecuteAppFunctionCallback executeAppFunctionCallback) {
        Objects.requireNonNull(requestInternal);
        Objects.requireNonNull(executeAppFunctionCallback);

        final SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback =
                new SafeOneTimeExecuteAppFunctionCallback(executeAppFunctionCallback);

        String validatedCallingPackage = mCallerValidator
                .validateCallingPackage(requestInternal.getCallingPackage());
        UserHandle targetUser = mCallerValidator.verifyTargetUserHandle(
                requestInternal.getUserHandle(), validatedCallingPackage);

        // TODO(b/354956319): Add and honor the new enterprise policies.
        if (mCallerValidator.isUserOrganizationManaged(targetUser)) {
            safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
                    ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
                    "Cannot run on a device with a device owner or from the managed profile."
            ).build());
            return;
        }

        String targetPackageName = requestInternal.getClientRequest().getTargetPackageName();
        if (TextUtils.isEmpty(targetPackageName)) {
            safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
                    ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT,
                    "Target package name cannot be empty."
            ).build());
            return;
        }

        if (!mCallerValidator.verifyCallerCanExecuteAppFunction(
                validatedCallingPackage, targetPackageName)) {
            throw new SecurityException("Caller does not have permission to execute the app "
                    + "function.");
        }

        Intent serviceIntent = mInternalServiceHelper.resolveAppFunctionService(
                targetPackageName,
                targetUser);
        if (serviceIntent == null) {
            safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
                    ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
                    "Cannot find the target service."
            ).build());
            return;
        }

        // TODO(b/357551503): Offload call to async executor.
        bindAppFunctionServiceUnchecked(requestInternal, serviceIntent, targetUser,
                safeExecuteAppFunctionCallback,
                /*bindFlags=*/ Context.BIND_AUTO_CREATE,
                // TODO(b/357551503): Make timeout configurable.
                /*timeoutInMillis=*/ 30_000L);
    }

    private void bindAppFunctionServiceUnchecked(
            @NonNull ExecuteAppFunctionAidlRequest requestInternal,
            @NonNull Intent serviceIntent, @NonNull UserHandle targetUser,
            @NonNull SafeOneTimeExecuteAppFunctionCallback
                    safeExecuteAppFunctionCallback,
            int bindFlags, long timeoutInMillis) {
        boolean bindServiceResult = mExternalServiceCallHelper.runServiceCall(
                serviceIntent,
                bindFlags,
                timeoutInMillis,
                targetUser,
                /*timeOutCallback=*/ new RunServiceCallCallback<IAppFunctionService>() {
                    @Override
                    public void onServiceConnected(@NonNull IAppFunctionService service,
                                                   @NonNull ServiceUsageCompleteListener
                                                           serviceUsageCompleteListener) {
                        try {
                            service.executeAppFunction(
                                    requestInternal.getClientRequest(),
                                    new IExecuteAppFunctionCallback.Stub() {
                                        @Override
                                        public void onResult(ExecuteAppFunctionResponse response) {
                                            safeExecuteAppFunctionCallback.onResult(response);
                                            serviceUsageCompleteListener.onCompleted();
                                        }
                                    }
                            );
                        } catch (Exception e) {
                            safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
                                    .Builder(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
                                    e.getMessage()).build());
                            serviceUsageCompleteListener.onCompleted();
                        }
                    }

                    @Override
                    public void onFailedToConnect() {
                        Slog.e(TAG, "Failed to connect to service");
                        safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
                                .Builder(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
                                "Failed to connect to AppFunctionService").build());
                    }

                    @Override
                    public void onTimedOut() {
                        Slog.e(TAG, "Timed out");
                        safeExecuteAppFunctionCallback.onResult(
                                new ExecuteAppFunctionResponse.Builder(
                                        ExecuteAppFunctionResponse.RESULT_TIMED_OUT,
                                        "Binding to AppFunctionService timed out."
                                ).build());
                    }
                }
        );

        if (!bindServiceResult) {
            Slog.e(TAG, "Failed to bind to the AppFunctionService");
            safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
                    ExecuteAppFunctionResponse.RESULT_TIMED_OUT,
                    "Failed to bind the AppFunctionService."
            ).build());
        }
    }
}
+81 −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.Manifest;
import android.annotation.NonNull;
import android.os.UserHandle;

import com.android.internal.annotations.VisibleForTesting;


/**
 * Interface for validating that the caller has the correct privilege to call an AppFunctionManager
 * API.
 */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public interface CallerValidator {
    // TODO(b/357551503): Should we verify NOT instant app?
    // TODO(b/357551503): Verify that user have been unlocked.

    /**
     * This method is used to validate that the calling package reported in the request is the
     * same as the binder calling identity.
     *
     * @param claimedCallingPackage The package name of the caller.
     * @return The package name of the caller.
     * @throws SecurityException if the package name and uid don't match.
     */
    String validateCallingPackage(@NonNull String claimedCallingPackage);

    /**
     * Validates that the caller can invoke an AppFunctionManager API in the provided
     * target user space.
     *
     * @param targetUserHandle      The user which the caller is requesting to execute as.
     * @param claimedCallingPackage The package name of the caller.
     * @return The user handle that the call should run as. Will always be a concrete user.
     * @throws IllegalArgumentException if the target user is a special user.
     * @throws SecurityException        if caller trying to interact across users without {@link
     *                                  Manifest.permission#INTERACT_ACROSS_USERS_FULL}
     */
    UserHandle verifyTargetUserHandle(@NonNull UserHandle targetUserHandle,
                                      @NonNull String claimedCallingPackage);

    /**
     * Validates that the caller can execute the specified app function.
     * <p>
     * The caller can execute if the app function's package name is the same as the caller's package
     * or the caller has either {@link Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or
     * {@link Manifest.permission.EXECUTE_APP_FUNCTIONS} granted. In some cases, app functions
     * can still opt-out of caller having {@link Manifest.permission.EXECUTE_APP_FUNCTIONS}.
     *
     * @param callerPackageName     The calling package (as previously validated).
     * @param targetPackageName     The package that owns the app function to execute.
     * @return Whether the caller can execute the specified app function.
     */
    boolean verifyCallerCanExecuteAppFunction(
            @NonNull String callerPackageName, @NonNull String targetPackageName);

    /**
     * Checks if the user is organization managed.
     *
     * @param targetUser The user which the caller is requesting to execute as.
     * @return Whether the user is organization managed.
     */
    boolean isUserOrganizationManaged(@NonNull UserHandle targetUser);
}
Loading