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

Commit 3cc31bfc authored by Aldi Fahrezi's avatar Aldi Fahrezi Committed by Oluwarotimi Adesina
Browse files

implementing AFMS.executeAppFunction

Flag: android.app.appfunctions.flags.enable_app_function_manager
Test: manual
Bug: 357551503
Change-Id: I670be62d76c386d4fcfd91281c4159433d2f4206
parent 48d98a08
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