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

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

Merge "Add isAppFunctionEnabled and setAppFunctionEnabled api to AFM" into main

parents 28cdd6e5 4f3f8042
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -8777,6 +8777,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 {
@@ -8818,6 +8823,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