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

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

Merge changes from topics "halo-split", "to-err" into main

* changes:
  Split the isAppFunctionEnabled method into two overloads
  Add Error categories for AppFunction exe response
parents 11e9c08f 67167c3b
Loading
Loading
Loading
Loading
+13 −7
Original line number Diff line number Diff line
@@ -8784,7 +8784,8 @@ package android.app.appfunctions {
  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
    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 @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) 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 isAppFunctionEnabled(@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
@@ -8817,6 +8818,7 @@ package android.app.appfunctions {
  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionResponse implements android.os.Parcelable {
    method public int describeContents();
    method public int getErrorCategory();
    method @Nullable public String getErrorMessage();
    method @NonNull public android.os.Bundle getExtras();
    method public int getResultCode();
@@ -8826,13 +8828,17 @@ package android.app.appfunctions {
    method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
    field public static final int ERROR_CATEGORY_APP = 3; // 0x3
    field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
    field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
    field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
    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_CANCELLED = 6; // 0x6
    field public static final int RESULT_DENIED = 1; // 0x1
    field public static final int RESULT_DISABLED = 5; // 0x5
    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_APP_UNKNOWN_ERROR = 3000; // 0xbb8
    field public static final int RESULT_CANCELLED = 2001; // 0x7d1
    field public static final int RESULT_DENIED = 1000; // 0x3e8
    field public static final int RESULT_DISABLED = 1002; // 0x3ea
    field public static final int RESULT_INTERNAL_ERROR = 2000; // 0x7d0
    field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9
    field public static final int RESULT_OK = 0; // 0x0
  }
+65 −22
Original line number Diff line number Diff line
@@ -53,8 +53,8 @@ import java.util.function.Consumer;
 * offers a more convenient and type-safe way to build app functions. The SDK provides predefined
 * function schemas for common use cases and associated data classes for function parameters and
 * return values. Apps only have to implement the provided interfaces. Internally, the SDK converts
 * these data classes into {@link ExecuteAppFunctionRequest#getParameters()} and
 * {@link ExecuteAppFunctionResponse#getResultDocument()}.
 * these data classes into {@link ExecuteAppFunctionRequest#getParameters()} and {@link
 * ExecuteAppFunctionResponse#getResultDocument()}.
 *
 * <p>**Discovering App Functions:**
 *
@@ -69,13 +69,12 @@ import java.util.function.Consumer;
 * <p>**Executing App Functions:**
 *
 * <p>To execute an app function, the caller app can retrieve the {@code functionIdentifier} from
 * the {@code AppFunctionStaticMetadata} document and use it to build an
 * {@link ExecuteAppFunctionRequest}. Then, invoke {@link #executeAppFunction} with the request
 * to execute the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS}
 * or {@code  android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} permission to execute app
 * functions from other apps. An app can always execute its own app functions and doesn't need these
 * permissions. AppFunction SDK provides a convenient way to achieve this and is the preferred
 * method.
 * the {@code AppFunctionStaticMetadata} document and use it to build an {@link
 * ExecuteAppFunctionRequest}. Then, invoke {@link #executeAppFunction} with the request to execute
 * the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS} or {@code
 * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} permission to execute app functions from other
 * apps. An app can always execute its own app functions and doesn't need these permissions.
 * AppFunction SDK provides a convenient way to achieve this and is the preferred method.
 *
 * <p>**Example:**
 *
@@ -213,12 +212,13 @@ public final class AppFunctionManager {
    /**
     * 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
     * <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:
     * <p>If the 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
@@ -232,23 +232,47 @@ public final class AppFunctionManager {
     * @param executor the executor to run the request
     * @param callback the callback to receive the function enabled check result
     */
    @RequiresPermission(
            anyOf = {
                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
                Manifest.permission.EXECUTE_APP_FUNCTIONS
            },
            conditional = true)
    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;
        isAppFunctionEnabledInternal(functionIdentifier, targetPackage, executor, callback);
    }

        AppFunctionManagerHelper.isAppFunctionEnabled(
                functionIdentifier, targetPackage, appSearchManager, executor, callback);
    /**
     * 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, unlike {@link
     * #isAppFunctionEnabled(String, String, Executor, OutcomeReceiver)}, which allows specifying a
     * different target package.
     *
     * <p>If the 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 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 Executor executor,
            @NonNull OutcomeReceiver<Boolean, Exception> callback) {
        isAppFunctionEnabledInternal(
                functionIdentifier, mContext.getPackageName(), executor, callback);
    }

    /**
@@ -291,6 +315,25 @@ public final class AppFunctionManager {
        }
    }

    private void isAppFunctionEnabledInternal(
            @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);
    }

    private static class CallbackWrapper extends IAppFunctionEnabledCallback.Stub {

        private final OutcomeReceiver<Void, Exception> mCallback;
+119 −14
Original line number Diff line number Diff line
@@ -73,38 +73,97 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
     */
    public static final String PROPERTY_RETURN_VALUE = "returnValue";

    /** The call was successful. */
    /**
     * The call was successful.
     *
     * <p>This result code does not belong in an error category.
     */
    public static final int RESULT_OK = 0;

    /** The caller does not have the permission to execute an app function. */
    public static final int RESULT_DENIED = 1;
    /**
     * The caller does not have the permission to execute an app function.
     *
     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
     */
    public static final int RESULT_DENIED = 1000;

    /**
     * The caller supplied invalid arguments to the execution request.
     *
     * <p>This error may be considered similar to {@link IllegalArgumentException}.
     *
     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
     */
    public static final int RESULT_INVALID_ARGUMENT = 1001;

    /**
     * The caller tried to execute a disabled app function.
     *
     * <p>This error is in the request error category.
     *
     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
     */
    public static final int RESULT_DISABLED = 1002;

    /**
     * An internal unexpected error coming from the system.
     *
     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
     */
    public static final int RESULT_INTERNAL_ERROR = 2000;

    /**
     * The operation was cancelled. Use this error code to report that a cancellation is done after
     * receiving a cancellation signal.
     *
     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
     */
    public static final int RESULT_CANCELLED = 2001;

    /**
     * An unknown error occurred while processing the call in the AppFunctionService.
     *
     * <p>This error is thrown when the service is connected in the remote application but an
     * unexpected error is thrown from the bound application.
     *
     * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
     */
    public static final int RESULT_APP_UNKNOWN_ERROR = 2;
    public static final int RESULT_APP_UNKNOWN_ERROR = 3000;

    /** An internal unexpected error coming from the system. */
    public static final int RESULT_INTERNAL_ERROR = 3;
    /**
     * The error category is unknown.
     *
     * <p>This is the default value for {@link #getErrorCategory}.
     */
    public static final int ERROR_CATEGORY_UNKNOWN = 0;

    /**
     * The caller supplied invalid arguments to the call.
     * The error is caused by the app requesting a function execution.
     *
     * <p>This error may be considered similar to {@link IllegalArgumentException}.
     * <p>For example, the caller provided invalid parameters in the execution request e.g. an
     * invalid function ID.
     *
     * <p>Errors in the category fall in the range 1000-1999 inclusive.
     */
    public static final int RESULT_INVALID_ARGUMENT = 4;
    public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;

    /** The caller tried to execute a disabled app function. */
    public static final int RESULT_DISABLED = 5;
    /**
     * The error is caused by an issue in the system.
     *
     * <p>For example, the AppFunctionService implementation is not found by the system.
     *
     * <p>Errors in the category fall in the range 2000-2999 inclusive.
     */
    public static final int ERROR_CATEGORY_SYSTEM = 2;

    /**
     * The operation was cancelled. Use this error code to report that a cancellation is done after
     * receiving a cancellation signal.
     * The error is caused by the app providing the function.
     *
     * <p>For example, the app crashed when the system is executing the request.
     *
     * <p>Errors in the category fall in the range 3000-3999 inclusive.
     */
    public static final int RESULT_CANCELLED = 6;
    public static final int ERROR_CATEGORY_APP = 3;

    /** The result code of the app function execution. */
    @ResultCode private final int mResultCode;
@@ -197,6 +256,36 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
        return extras;
    }

    /**
     * Returns the error category of the {@link ExecuteAppFunctionResponse}.
     *
     * <p>This method categorizes errors based on their underlying cause, allowing developers to
     * implement targeted error handling and provide more informative error messages to users. It
     * maps ranges of result codes to specific error categories.
     *
     * <p>When constructing a {@link #newFailure} response, use the appropriate result code value to
     * ensure correct categorization of the failed response.
     *
     * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the result code does not belong to
     * any error category, for example, in the case of a successful result with {@link #RESULT_OK}.
     *
     * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
     * result code ranges.
     */
    @ErrorCategory
    public int getErrorCategory() {
        if (mResultCode >= 1000 && mResultCode < 2000) {
            return ERROR_CATEGORY_REQUEST_ERROR;
        }
        if (mResultCode >= 2000 && mResultCode < 3000) {
            return ERROR_CATEGORY_SYSTEM;
        }
        if (mResultCode >= 3000 && mResultCode < 4000) {
            return ERROR_CATEGORY_APP;
        }
        return ERROR_CATEGORY_UNKNOWN;
    }

    /**
     * Returns a generic document containing the return value of the executed function.
     *
@@ -287,4 +376,20 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
            })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ResultCode {}

    /**
     * Error categories.
     *
     * @hide
     */
    @IntDef(
            prefix = {"ERROR_CATEGORY_"},
            value = {
                ERROR_CATEGORY_UNKNOWN,
                ERROR_CATEGORY_REQUEST_ERROR,
                ERROR_CATEGORY_APP,
                ERROR_CATEGORY_SYSTEM
            })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ErrorCategory {}
}
+14 −8
Original line number Diff line number Diff line
@@ -3,8 +3,9 @@ package com.google.android.appfunctions.sidecar {

  public final class AppFunctionManager {
    ctor public AppFunctionManager(android.content.Context);
    method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.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 @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
    method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) 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 isAppFunctionEnabled(@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
@@ -34,6 +35,7 @@ package com.google.android.appfunctions.sidecar {
  }

  public final class ExecuteAppFunctionResponse {
    method public int getErrorCategory();
    method @Nullable public String getErrorMessage();
    method @NonNull public android.os.Bundle getExtras();
    method public int getResultCode();
@@ -41,13 +43,17 @@ package com.google.android.appfunctions.sidecar {
    method public boolean isSuccess();
    method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
    method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
    field public static final int ERROR_CATEGORY_APP = 3; // 0x3
    field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
    field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
    field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
    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_CANCELLED = 6; // 0x6
    field public static final int RESULT_DENIED = 1; // 0x1
    field public static final int RESULT_DISABLED = 5; // 0x5
    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_APP_UNKNOWN_ERROR = 3000; // 0xbb8
    field public static final int RESULT_CANCELLED = 2001; // 0x7d1
    field public static final int RESULT_DENIED = 1000; // 0x3e8
    field public static final int RESULT_DISABLED = 1002; // 0x3ea
    field public static final int RESULT_INTERNAL_ERROR = 2000; // 0x7d0
    field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9
    field public static final int RESULT_OK = 0; // 0x0
  }

+27 −0
Original line number Diff line number Diff line
@@ -16,9 +16,11 @@

package com.google.android.appfunctions.sidecar;

import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.UserHandleAware;
import android.content.Context;
@@ -103,6 +105,12 @@ public final class AppFunctionManager {
     * <p>See {@link android.app.appfunctions.AppFunctionManager#executeAppFunction} for the
     * documented behaviour of this method.
     */
    @RequiresPermission(
            anyOf = {
                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
                Manifest.permission.EXECUTE_APP_FUNCTIONS
            },
            conditional = true)
    public void executeAppFunction(
            @NonNull ExecuteAppFunctionRequest sidecarRequest,
            @NonNull @CallbackExecutor Executor executor,
@@ -131,6 +139,12 @@ public final class AppFunctionManager {
     * <p>See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the
     * documented behaviour of this method.
     */
    @RequiresPermission(
            anyOf = {
                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
                Manifest.permission.EXECUTE_APP_FUNCTIONS
            },
            conditional = true)
    public void isAppFunctionEnabled(
            @NonNull String functionIdentifier,
            @NonNull String targetPackage,
@@ -139,6 +153,19 @@ public final class AppFunctionManager {
        mManager.isAppFunctionEnabled(functionIdentifier, targetPackage, executor, callback);
    }

    /**
     * Returns a boolean through a callback, indicating whether the app function is enabled.
     *
     * <p>See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the
     * documented behaviour of this method.
     */
    public void isAppFunctionEnabled(
            @NonNull String functionIdentifier,
            @NonNull Executor executor,
            @NonNull OutcomeReceiver<Boolean, Exception> callback) {
        mManager.isAppFunctionEnabled(functionIdentifier, executor, callback);
    }

    /**
     * Sets the enabled state of the app function owned by the calling package.
     *
Loading