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

Commit 4f3f8042 authored by Tony Mak's avatar Tony Mak
Browse files

Add isAppFunctionEnabled and setAppFunctionEnabled api to AFM

Flag: android.app.appfunctions.flags.enable_app_function_manager
Test: atest CtsAppFunctionTestCases -c
BUG: 357551503

Change-Id: Ib27306ba8602d6674e39421416a35583ab8030ab
parent 0f4f38ce
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -8775,6 +8775,11 @@ package android.app.appfunctions {
  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
    method @Deprecated @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
    method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
    method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
    method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
    field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
    field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2
    field public static final int APP_FUNCTION_STATE_ENABLED = 1; // 0x1
  }
  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
@@ -8816,6 +8821,7 @@ package android.app.appfunctions {
    field public static final String PROPERTY_RETURN_VALUE = "returnValue";
    field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
    field public static final int RESULT_DENIED = 1; // 0x1
    field public static final int RESULT_DISABLED = 6; // 0x6
    field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
    field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
    field public static final int RESULT_OK = 0; // 0x0
+154 −0
Original line number Diff line number Diff line
@@ -22,15 +22,21 @@ import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANA
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.UserHandleAware;
import android.app.appsearch.AppSearchManager;
import android.content.Context;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
import android.os.ParcelableException;
import android.os.RemoteException;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -45,9 +51,43 @@ import java.util.function.Consumer;
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
@SystemService(Context.APP_FUNCTION_SERVICE)
public final class AppFunctionManager {

    /**
     * The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset
     * enabled state to the default value.
     */
    public static final int APP_FUNCTION_STATE_DEFAULT = 0;

    /**
     * The app function is enabled. To enable an app function, call {@link #setAppFunctionEnabled}
     * with this value.
     */
    public static final int APP_FUNCTION_STATE_ENABLED = 1;

    /**
     * The app function is disabled. To disable an app function, call {@link #setAppFunctionEnabled}
     * with this value.
     */
    public static final int APP_FUNCTION_STATE_DISABLED = 2;

    private final IAppFunctionManager mService;
    private final Context mContext;

    /**
     * The enabled state of the app function.
     *
     * @hide
     */
    @IntDef(
            prefix = {"APP_FUNCTION_STATE_"},
            value = {
                APP_FUNCTION_STATE_DEFAULT,
                APP_FUNCTION_STATE_ENABLED,
                APP_FUNCTION_STATE_DISABLED
            })
    @Retention(RetentionPolicy.SOURCE)
    public @interface EnabledState {}

    /**
     * Creates an instance.
     *
@@ -164,4 +204,118 @@ public final class AppFunctionManager {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns a boolean through a callback, indicating whether the app function is enabled.
     *
     * <p>* This method can only check app functions owned by the caller, or those where the caller
     * has visibility to the owner package and holds either the {@link
     * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
     * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
     *
     * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
     *
     * <ul>
     *   <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
     *       have access to it.
     * </ul>
     *
     * @param functionIdentifier the identifier of the app function to check (unique within the
     *     target package) and in most cases, these are automatically generated by the AppFunctions
     *     SDK
     * @param targetPackage the package name of the app function's owner
     * @param executor the executor to run the request
     * @param callback the callback to receive the function enabled check result
     */
    public void isAppFunctionEnabled(
            @NonNull String functionIdentifier,
            @NonNull String targetPackage,
            @NonNull Executor executor,
            @NonNull OutcomeReceiver<Boolean, Exception> callback) {
        Objects.requireNonNull(functionIdentifier);
        Objects.requireNonNull(targetPackage);
        Objects.requireNonNull(executor);
        Objects.requireNonNull(callback);
        AppSearchManager appSearchManager = mContext.getSystemService(AppSearchManager.class);
        if (appSearchManager == null) {
            callback.onError(new IllegalStateException("Failed to get AppSearchManager."));
            return;
        }

        AppFunctionManagerHelper.isAppFunctionEnabled(
                functionIdentifier, targetPackage, appSearchManager, executor, callback);
    }

    /**
     * Sets the enabled state of the app function owned by the calling package.
     *
     * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
     *
     * <ul>
     *   <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
     *       have access to it.
     * </ul>
     *
     * @param functionIdentifier the identifier of the app function to enable (unique within the
     *     calling package). In most cases, identifiers are automatically generated by the
     *     AppFunctions SDK
     * @param newEnabledState the new state of the app function
     * @param executor the executor to run the callback
     * @param callback the callback to receive the result of the function enablement. The call was
     *     successful if no exception was thrown.
     */
    @UserHandleAware
    public void setAppFunctionEnabled(
            @NonNull String functionIdentifier,
            @EnabledState int newEnabledState,
            @NonNull Executor executor,
            @NonNull OutcomeReceiver<Void, Exception> callback) {
        Objects.requireNonNull(functionIdentifier);
        Objects.requireNonNull(executor);
        Objects.requireNonNull(callback);
        CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback);
        try {
            mService.setAppFunctionEnabled(
                    mContext.getPackageName(),
                    functionIdentifier,
                    mContext.getUser(),
                    newEnabledState,
                    callbackWrapper);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    private static class CallbackWrapper extends IAppFunctionEnabledCallback.Stub {

        private final OutcomeReceiver<Void, Exception> mCallback;
        private final Executor mExecutor;

        CallbackWrapper(
                @NonNull Executor callbackExecutor,
                @NonNull OutcomeReceiver<Void, Exception> callback) {
            mCallback = callback;
            mExecutor = callbackExecutor;
        }

        @Override
        public void onSuccess() {
            mExecutor.execute(() -> mCallback.onResult(null));
        }

        @Override
        public void onError(@NonNull ParcelableException exception) {
            mExecutor.execute(() -> {
                if (IllegalArgumentException.class.isAssignableFrom(
                        exception.getCause().getClass())) {
                    mCallback.onError((IllegalArgumentException) exception.getCause());
                } else if (SecurityException.class.isAssignableFrom(
                        exception.getCause().getClass())) {
                    mCallback.onError((SecurityException) exception.getCause());
                } else {
                    mCallback.onError(exception);
                }
            });
        }
    }
}
+81 −79
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@ import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCT
import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_ENABLED_BY_DEFAULT;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;

import android.annotation.CallbackExecutor;
import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchManager;
@@ -33,6 +33,7 @@ import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchSpec;
import android.os.OutcomeReceiver;
import android.text.TextUtils;

import java.io.IOException;
import java.util.List;
@@ -50,23 +51,24 @@ public class AppFunctionManagerHelper {
    /**
     * Returns (through a callback) a boolean indicating whether the app function is enabled.
     *
     * <p>This method can only check app functions that are owned by the caller owned by packages
     * visible to the caller.
     * This method can only check app functions owned by the caller, or those where the caller
     * has visibility to the owner package and holds either the {@link
     * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
     * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
     *
     * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
     *
     * <ul>
     *   <li>{@link IllegalArgumentException}, if the function is not found
     *   <li>{@link SecurityException}, if the caller does not have permission to query the target
     *       package
     *   <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
     *       have access to it.
     * </ul>
     *
     * @param functionIdentifier the identifier of the app function to check (unique within the
     *     target package) and in most cases, these are automatically generated by the AppFunctions
     *                           target package) and in most cases, these are automatically
     *                           generated by the AppFunctions
     *                           SDK
     * @param targetPackage      the package name of the app function's owner
     * @param appSearchExecutor the executor to run the metadata search mechanism through AppSearch
     * @param callbackExecutor the executor to run the callback
     * @param executor           executor the executor to run the request
     * @param callback           the callback to receive the function enabled check result
     * @hide
     */
@@ -74,49 +76,44 @@ public class AppFunctionManagerHelper {
            @NonNull String functionIdentifier,
            @NonNull String targetPackage,
            @NonNull AppSearchManager appSearchManager,
            @NonNull Executor appSearchExecutor,
            @NonNull @CallbackExecutor Executor callbackExecutor,
            @NonNull Executor executor,
            @NonNull OutcomeReceiver<Boolean, Exception> callback) {
        Objects.requireNonNull(functionIdentifier);
        Objects.requireNonNull(targetPackage);
        Objects.requireNonNull(appSearchManager);
        Objects.requireNonNull(appSearchExecutor);
        Objects.requireNonNull(callbackExecutor);
        Objects.requireNonNull(executor);
        Objects.requireNonNull(callback);

        appSearchManager.createGlobalSearchSession(
                appSearchExecutor,
                executor,
                (searchSessionResult) -> {
                    if (!searchSessionResult.isSuccess()) {
                        callbackExecutor.execute(
                                () ->
                                        callback.onError(
                                                failedResultToException(searchSessionResult)));
                        callback.onError(failedResultToException(searchSessionResult));
                        return;
                    }
                    try (GlobalSearchSession searchSession = searchSessionResult.getResultValue()) {
                        SearchResults results =
                                searchJoinedStaticWithRuntimeAppFunctions(
                                        searchSession, targetPackage, functionIdentifier);
                                        Objects.requireNonNull(searchSession),
                                        targetPackage,
                                        functionIdentifier);
                        results.getNextPage(
                                appSearchExecutor,
                                listAppSearchResult ->
                                        callbackExecutor.execute(
                                                () -> {
                                executor,
                                listAppSearchResult -> {
                                    if (listAppSearchResult.isSuccess()) {
                                        callback.onResult(
                                                                getEnabledStateFromSearchResults(
                                                getEffectiveEnabledStateFromSearchResults(
                                                        Objects.requireNonNull(
                                                                listAppSearchResult
                                                                        .getResultValue())));
                                    } else {
                                        callback.onError(
                                                                failedResultToException(
                                                                        listAppSearchResult));
                                                failedResultToException(listAppSearchResult));
                                    }
                                                }));
                                });
                        results.close();
                    } catch (Exception e) {
                        callbackExecutor.execute(() -> callback.onError(e));
                        callback.onError(e);
                    }
                });
    }
@@ -124,56 +121,58 @@ public class AppFunctionManagerHelper {
    /**
     * Searches joined app function static and runtime metadata using the function Id and the
     * package.
     *
     * @hide
     */
    private static @NonNull SearchResults searchJoinedStaticWithRuntimeAppFunctions(
            @NonNull GlobalSearchSession session,
            @NonNull String targetPackage,
            @NonNull String functionIdentifier) {
        SearchSpec runtimeSearchSpec =
                getAppFunctionRuntimeMetadataSearchSpecByFunctionId(targetPackage);
                getAppFunctionRuntimeMetadataSearchSpecByPackageName(targetPackage);
        JoinSpec joinSpec =
                new JoinSpec.Builder(PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID)
                        .setNestedSearch(functionIdentifier, runtimeSearchSpec)
                        .setNestedSearch(
                                buildFilerRuntimeMetadataByFunctionIdQuery(functionIdentifier),
                                runtimeSearchSpec)
                        .build();
        SearchSpec joinedStaticWithRuntimeSearchSpec =
                new SearchSpec.Builder()
                        .setJoinSpec(joinSpec)
                        .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
                        .addFilterSchemas(
                                AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage(
                                        targetPackage))
                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
                        .setJoinSpec(joinSpec)
                        .setVerbatimSearchEnabled(true)
                        .build();
        return session.search(functionIdentifier, joinedStaticWithRuntimeSearchSpec);
        return session.search(
                buildFilerStaticMetadataByFunctionIdQuery(functionIdentifier),
                joinedStaticWithRuntimeSearchSpec);
    }

    /**
     * Finds whether the function is enabled or not from the search results returned by {@link
     * #searchJoinedStaticWithRuntimeAppFunctions}.
     * Returns whether the function is effectively enabled or not from the search results returned
     * by {@link #searchJoinedStaticWithRuntimeAppFunctions}.
     *
     * @param joinedStaticRuntimeResults search results joining AppFunctionStaticMetadata
     *                                   and AppFunctionRuntimeMetadata.
     * @throws IllegalArgumentException if the function is not found in the results
     * @hide
     */
    private static boolean getEnabledStateFromSearchResults(
    private static boolean getEffectiveEnabledStateFromSearchResults(
            @NonNull List<SearchResult> joinedStaticRuntimeResults) {
        if (joinedStaticRuntimeResults.isEmpty()) {
            // Function not found.
            throw new IllegalArgumentException("App function not found.");
        } else {
            List<SearchResult> runtimeMetadataResults =
                    joinedStaticRuntimeResults.getFirst().getJoinedResults();
            if (!runtimeMetadataResults.isEmpty()) {
                Boolean result =
                        (Boolean)
            if (runtimeMetadataResults.isEmpty()) {
                throw new IllegalArgumentException("App function not found.");
            }
            boolean[] enabled =
                    runtimeMetadataResults
                            .getFirst()
                            .getGenericDocument()
                                        .getProperty(PROPERTY_ENABLED);
                if (result != null) {
                    return result;
                }
                            .getPropertyBooleanArray(PROPERTY_ENABLED);
            if (enabled != null && enabled.length != 0) {
                return enabled[0];
            }
            // Runtime metadata not found. Using the default value in the static metadata.
            return joinedStaticRuntimeResults
@@ -186,36 +185,39 @@ public class AppFunctionManagerHelper {
    /**
     * Returns search spec that queries app function metadata for a specific package name by its
     * function identifier.
     *
     * @hide
     */
    public static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByFunctionId(
    private static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByPackageName(
            @NonNull String targetPackage) {
        return new SearchSpec.Builder()
                .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
                .addFilterSchemas(
                        AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(targetPackage))
                .addFilterProperties(
                        AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(targetPackage),
                        List.of(AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID))
                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
                .setVerbatimSearchEnabled(true)
                .build();
    }

    /**
     * Converts a failed app search result codes into an exception.
     *
     * @hide
     */
    public static @NonNull Exception failedResultToException(
    private static String buildFilerRuntimeMetadataByFunctionIdQuery(String functionIdentifier) {
        return TextUtils.formatSimple("%s:\"%s\"",
                AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID,
                functionIdentifier);
    }

    private static String buildFilerStaticMetadataByFunctionIdQuery(String functionIdentifier) {
        return TextUtils.formatSimple("%s:\"%s\"",
                AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID,
                functionIdentifier);
    }

    /** Converts a failed app search result codes into an exception. */
    private static @NonNull Exception failedResultToException(
            @NonNull AppSearchResult appSearchResult) {
        return switch (appSearchResult.getResultCode()) {
            case AppSearchResult.RESULT_INVALID_ARGUMENT ->
                    new IllegalArgumentException(appSearchResult.getErrorMessage());
            case AppSearchResult.RESULT_IO_ERROR ->
                    new IOException(appSearchResult.getErrorMessage());
            case AppSearchResult.RESULT_SECURITY_ERROR ->
                    new SecurityException(appSearchResult.getErrorMessage());
            case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException(
                    appSearchResult.getErrorMessage());
            case AppSearchResult.RESULT_IO_ERROR -> new IOException(
                    appSearchResult.getErrorMessage());
            case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException(
                    appSearchResult.getErrorMessage());
            default -> new IllegalStateException(appSearchResult.getErrorMessage());
        };
    }
+8 −2
Original line number Diff line number Diff line
@@ -204,11 +204,17 @@ public class AppFunctionRuntimeMetadata extends GenericDocument {
                            packageName, functionId));
        }

        public Builder(AppFunctionRuntimeMetadata original) {
            this(original.getPackageName(), original.getFunctionId());
            setEnabled(original.getEnabled());
        }

        /**
         * Sets an indicator specifying if the function is enabled or not. This would override the
         * default enabled state in the static metadata ({@link
         * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to
         * null to clear the override.
         * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to null
         * to clear the override.
         * TODO(369683073) Replace the tristate Boolean with IntDef EnabledState.
         */
        @NonNull
        public Builder setEnabled(@Nullable Boolean enabled) {
+4 −0
Original line number Diff line number Diff line
@@ -99,6 +99,9 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
    /** The operation was timed out. */
    public static final int RESULT_TIMED_OUT = 5;

    /** The caller tried to execute a disabled app function. */
    public static final int RESULT_DISABLED = 6;

    /** The result code of the app function execution. */
    @ResultCode private final int mResultCode;

@@ -274,6 +277,7 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
                RESULT_INTERNAL_ERROR,
                RESULT_INVALID_ARGUMENT,
                RESULT_TIMED_OUT,
                RESULT_DISABLED,
            })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ResultCode {}
Loading