Add Error categories for AppFunction exe response

Flag: android.app.appfunctions.flags.enable_app_function_manager
Test: existing CTS
Bug: 357551503
Change-Id: I9209234f28150ebde02886a1dddce0a218d43db0
diff --git a/core/api/current.txt b/core/api/current.txt
index f267131..27c3cc5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8816,6 +8816,7 @@
 
   @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();
@@ -8825,13 +8826,17 @@
     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
   }
 
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index 83453a9..7e34099 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -73,38 +73,97 @@
      */
     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.
-     */
-    public static final int RESULT_APP_UNKNOWN_ERROR = 2;
-
-    /** An internal unexpected error coming from the system. */
-    public static final int RESULT_INTERNAL_ERROR = 3;
-
-    /**
-     * The caller supplied invalid arguments to the call.
      *
-     * <p>This error may be considered similar to {@link IllegalArgumentException}.
+     * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
      */
-    public static final int RESULT_INVALID_ARGUMENT = 4;
-
-    /** The caller tried to execute a disabled app function. */
-    public static final int RESULT_DISABLED = 5;
+    public static final int RESULT_APP_UNKNOWN_ERROR = 3000;
 
     /**
-     * The operation was cancelled. Use this error code to report that a cancellation is done after
-     * receiving a cancellation signal.
+     * The error category is unknown.
+     *
+     * <p>This is the default value for {@link #getErrorCategory}.
      */
-    public static final int RESULT_CANCELLED = 6;
+    public static final int ERROR_CATEGORY_UNKNOWN = 0;
+
+    /**
+     * The error is caused by the app requesting a function execution.
+     *
+     * <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 ERROR_CATEGORY_REQUEST_ERROR = 1;
+
+    /**
+     * 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 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 ERROR_CATEGORY_APP = 3;
 
     /** The result code of the app function execution. */
     @ResultCode private final int mResultCode;
@@ -198,6 +257,36 @@
     }
 
     /**
+     * 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.
      *
      * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
@@ -287,4 +376,20 @@
             })
     @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 {}
 }
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index 27817e9..6c42bd3 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -34,6 +34,7 @@
   }
 
   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 +42,17 @@
     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
   }
 
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
index d5dfaeb..bf4ab45 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
@@ -50,38 +50,97 @@
      */
     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.
-     */
-    public static final int RESULT_APP_UNKNOWN_ERROR = 2;
-
-    /** An internal unexpected error coming from the system. */
-    public static final int RESULT_INTERNAL_ERROR = 3;
-
-    /**
-     * The caller supplied invalid arguments to the call.
      *
-     * <p>This error may be considered similar to {@link IllegalArgumentException}.
+     * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
      */
-    public static final int RESULT_INVALID_ARGUMENT = 4;
-
-    /** The caller tried to execute a disabled app function. */
-    public static final int RESULT_DISABLED = 5;
+    public static final int RESULT_APP_UNKNOWN_ERROR = 3000;
 
     /**
-     * The operation was cancelled. Use this error code to report that a cancellation is done after
-     * receiving a cancellation signal.
+     * The error category is unknown.
+     *
+     * <p>This is the default value for {@link #getErrorCategory}.
      */
-    public static final int RESULT_CANCELLED = 6;
+    public static final int ERROR_CATEGORY_UNKNOWN = 0;
+
+    /**
+     * The error is caused by the app requesting a function execution.
+     *
+     * <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 ERROR_CATEGORY_REQUEST_ERROR = 1;
+
+    /**
+     * 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 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 ERROR_CATEGORY_APP = 3;
 
     /** The result code of the app function execution. */
     @ResultCode private final int mResultCode;
@@ -171,6 +230,36 @@
     }
 
     /**
+     * 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.
      *
      * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
@@ -245,4 +334,20 @@
             })
     @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 {}
 }