ContextHubService: Implement CHRE reliable messages

API-Coverage-Bug: 312417087
Bug: 312417087
Test: Run the GtsContextHubReliableMessageNanoAppTest
Change-Id: Ibfaf6de5b79adf35e18c1e36a045863de6e34aaf
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index debf1bf..cb7aece 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5487,6 +5487,7 @@
     method @NonNull public android.hardware.location.ContextHubInfo getAttachedHub();
     method @IntRange(from=0, to=65535) public int getId();
     method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessageToNanoApp(@NonNull android.hardware.location.NanoAppMessage);
+    method @FlaggedApi("android.chre.flags.reliable_message") @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> sendReliableMessageToNanoApp(@NonNull android.hardware.location.NanoAppMessage);
   }
 
   public class ContextHubClientCallback {
@@ -5522,6 +5523,7 @@
     method public String getToolchain();
     method public int getToolchainVersion();
     method public String getVendor();
+    method @FlaggedApi("android.chre.flags.reliable_message") public boolean supportsReliableMessages();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.location.ContextHubInfo> CREATOR;
   }
@@ -5605,6 +5607,7 @@
     field public static final int RESULT_FAILED_BAD_PARAMS = 2; // 0x2
     field public static final int RESULT_FAILED_BUSY = 4; // 0x4
     field public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8; // 0x8
+    field @FlaggedApi("android.chre.flags.reliable_message") public static final int RESULT_FAILED_NOT_SUPPORTED = 9; // 0x9
     field public static final int RESULT_FAILED_SERVICE_INTERNAL_FAILURE = 7; // 0x7
     field public static final int RESULT_FAILED_TIMEOUT = 6; // 0x6
     field public static final int RESULT_FAILED_UNINITIALIZED = 3; // 0x3
@@ -5614,6 +5617,7 @@
     field public static final int TYPE_ENABLE_NANOAPP = 2; // 0x2
     field public static final int TYPE_LOAD_NANOAPP = 0; // 0x0
     field public static final int TYPE_QUERY_NANOAPPS = 4; // 0x4
+    field @FlaggedApi("android.chre.flags.reliable_message") public static final int TYPE_RELIABLE_MESSAGE = 5; // 0x5
     field public static final int TYPE_UNLOAD_NANOAPP = 1; // 0x1
   }
 
@@ -5796,12 +5800,17 @@
 
   public final class NanoAppMessage implements android.os.Parcelable {
     method public static android.hardware.location.NanoAppMessage createMessageFromNanoApp(long, int, byte[], boolean);
+    method @FlaggedApi("android.chre.flags.reliable_message") @NonNull public static android.hardware.location.NanoAppMessage createMessageFromNanoApp(long, int, @NonNull byte[], boolean, boolean, int);
     method public static android.hardware.location.NanoAppMessage createMessageToNanoApp(long, int, byte[]);
     method public int describeContents();
     method public byte[] getMessageBody();
+    method @FlaggedApi("android.chre.flags.reliable_message") public int getMessageSequenceNumber();
     method public int getMessageType();
     method public long getNanoAppId();
     method public boolean isBroadcastMessage();
+    method @FlaggedApi("android.chre.flags.reliable_message") public boolean isReliable();
+    method @FlaggedApi("android.chre.flags.reliable_message") public void setIsReliable(boolean);
+    method @FlaggedApi("android.chre.flags.reliable_message") public void setMessageSequenceNumber(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppMessage> CREATOR;
   }
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index 6ed87fff..01dccd1 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -15,11 +15,14 @@
  */
 package android.hardware.location;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.app.PendingIntent;
+import android.chre.flags.Flags;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -185,23 +188,75 @@
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @ContextHubTransaction.Result
     public int sendMessageToNanoApp(@NonNull NanoAppMessage message) {
+        return doSendMessageToNanoApp(message, null);
+    }
+
+    /**
+     * Sends a reliable message to a nanoapp.
+     *
+     * This method is similar to {@link ContextHubClient#sendMessageToNanoApp} with the
+     * difference that it expects the message to be acknowledged by CHRE.
+     *
+     * The transaction succeeds after we received an ACK from CHRE without error.
+     * In all other cases the transaction will fail.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @NonNull
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public ContextHubTransaction<Void> sendReliableMessageToNanoApp(
+            @NonNull NanoAppMessage message) {
+        if (!Flags.reliableMessageImplementation()) {
+            return null;
+        }
+
+        ContextHubTransaction<Void> transaction =
+                new ContextHubTransaction<>(ContextHubTransaction.TYPE_RELIABLE_MESSAGE);
+
+        if (!mAttachedHub.supportsReliableMessages()) {
+            transaction.setResponse(new ContextHubTransaction.Response<Void>(
+                    ContextHubTransaction.RESULT_FAILED_NOT_SUPPORTED, null));
+            return transaction;
+        }
+
+        IContextHubTransactionCallback callback =
+                ContextHubTransactionHelper.createTransactionCallback(transaction);
+
+        @ContextHubTransaction.Result int result = doSendMessageToNanoApp(message, callback);
+        if (result != ContextHubTransaction.RESULT_SUCCESS) {
+            transaction.setResponse(new ContextHubTransaction.Response<Void>(result, null));
+        }
+
+        return transaction;
+    }
+
+    /**
+     * Sends a message to a nanoapp.
+     *
+     * @param message The message to send.
+     * @param transactionCallback The callback to use when the message is reliable. null for regular
+     *         messages.
+     * @return A {@link ContextHubTransaction.Result} error code.
+     */
+    @ContextHubTransaction.Result
+    private int doSendMessageToNanoApp(@NonNull NanoAppMessage message,
+            @Nullable IContextHubTransactionCallback transactionCallback) {
         Objects.requireNonNull(message, "NanoAppMessage cannot be null");
 
         int maxPayloadBytes = mAttachedHub.getMaxPacketLengthBytes();
+
         byte[] payload = message.getMessageBody();
         if (payload != null && payload.length > maxPayloadBytes) {
-            Log.e(
-                    TAG,
-                    "Message ("
-                            + payload.length
-                            + " bytes) exceeds max payload length ("
-                            + maxPayloadBytes
-                            + " bytes)");
+            Log.e(TAG,
+                    "Message (%d bytes) exceeds max payload length (%d bytes)".formatted(
+                            payload.length, maxPayloadBytes));
             return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
         }
 
         try {
-            return mClientProxy.sendMessageToNanoApp(message);
+            if (transactionCallback == null) {
+                return mClientProxy.sendMessageToNanoApp(message);
+            }
+            return mClientProxy.sendReliableMessageToNanoApp(message, transactionCallback);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -224,16 +279,32 @@
     /** @hide */
     public synchronized void callbackFinished() {
         try {
-            while (mClientProxy == null) {
-                try {
-                    this.wait();
-                } catch (InterruptedException e) {
-                    Thread.currentThread().interrupt();
-                }
-            }
+            waitForClientProxy();
             mClientProxy.callbackFinished();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /** @hide */
+    public synchronized void reliableMessageCallbackFinished(int messageSequenceNumber,
+            byte errorCode) {
+        try {
+            waitForClientProxy();
+            mClientProxy.reliableMessageCallbackFinished(messageSequenceNumber, errorCode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    private void waitForClientProxy() {
+        while (mClientProxy == null) {
+            try {
+                this.wait();
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
 }
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index 51045a4..5012a79 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -15,9 +15,11 @@
  */
 package android.hardware.location;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.chre.flags.Flags;
 import android.hardware.contexthub.V1_0.ContextHub;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -41,6 +43,7 @@
     private float mSleepPowerDrawMw;
     private float mPeakPowerDrawMw;
     private int mMaxPacketLengthBytes;
+    private boolean mSupportsReliableMessages;
     private byte mChreApiMajorVersion;
     private byte mChreApiMinorVersion;
     private short mChrePatchVersion;
@@ -71,6 +74,7 @@
         mSleepPowerDrawMw = contextHub.sleepPowerDrawMw;
         mPeakPowerDrawMw = contextHub.peakPowerDrawMw;
         mMaxPacketLengthBytes = contextHub.maxSupportedMsgLen;
+        mSupportsReliableMessages = false;
         mChrePlatformId = contextHub.chrePlatformId;
         mChreApiMajorVersion = contextHub.chreApiMajorVersion;
         mChreApiMinorVersion = contextHub.chreApiMinorVersion;
@@ -94,6 +98,8 @@
         mSleepPowerDrawMw = 0;
         mPeakPowerDrawMw = 0;
         mMaxPacketLengthBytes = contextHub.maxSupportedMessageLengthBytes;
+        mSupportsReliableMessages = Flags.reliableMessageImplementation()
+                && contextHub.supportsReliableMessages;
         mChrePlatformId = contextHub.chrePlatformId;
         mChreApiMajorVersion = contextHub.chreApiMajorVersion;
         mChreApiMinorVersion = contextHub.chreApiMinorVersion;
@@ -104,16 +110,25 @@
     }
 
     /**
-     * returns the maximum number of bytes that can be sent per message to the hub
+     * Returns the maximum number of bytes for a message to the hub.
      *
-     * @return int - maximum bytes that can be transmitted in a
-     *         single packet
+     * @return int - maximum bytes that can be transmitted in a single packet.
      */
     public int getMaxPacketLengthBytes() {
         return mMaxPacketLengthBytes;
     }
 
     /**
+     * Returns whether reliable messages are supported
+     *
+     * @return whether reliable messages are supported.
+     */
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public boolean supportsReliableMessages() {
+        return mSupportsReliableMessages;
+    }
+
+    /**
      * get the context hub unique identifer
      *
      * @return int - unique system wide identifier
@@ -164,7 +179,10 @@
      * @return int - platform version number
      */
     public int getStaticSwVersion() {
-        return (mChreApiMajorVersion << 24) | (mChreApiMinorVersion << 16) | (mChrePatchVersion);
+        // Version parts are all unsigned values.
+        return (Byte.toUnsignedInt(mChreApiMajorVersion) << 24)
+                | (Byte.toUnsignedInt(mChreApiMinorVersion) << 16)
+                | (Short.toUnsignedInt(mChrePatchVersion));
     }
 
     /**
@@ -284,12 +302,14 @@
         retVal += ", Toolchain version: 0x" + Integer.toHexString(mToolchainVersion);
         retVal += "\n\tPlatformVersion : 0x" + Integer.toHexString(mPlatformVersion);
         retVal += ", SwVersion : "
-                + mChreApiMajorVersion + "." + mChreApiMinorVersion + "." + mChrePatchVersion;
+                + Byte.toUnsignedInt(mChreApiMajorVersion) + "." + Byte.toUnsignedInt(
+                mChreApiMinorVersion) + "." + Short.toUnsignedInt(mChrePatchVersion);
         retVal += ", CHRE platform ID: 0x" + Long.toHexString(mChrePlatformId);
         retVal += "\n\tPeakMips : " + mPeakMips;
         retVal += ", StoppedPowerDraw : " + mStoppedPowerDrawMw + " mW";
         retVal += ", PeakPowerDraw : " + mPeakPowerDrawMw + " mW";
         retVal += ", MaxPacketLength : " + mMaxPacketLengthBytes + " Bytes";
+        retVal += ", SupportsReliableMessage : " + mSupportsReliableMessages;
 
         return retVal;
     }
@@ -316,6 +336,8 @@
         proto.write(ContextHubInfoProto.SLEEP_POWER_DRAW_MW, mSleepPowerDrawMw);
         proto.write(ContextHubInfoProto.PEAK_POWER_DRAW_MW, mPeakPowerDrawMw);
         proto.write(ContextHubInfoProto.MAX_PACKET_LENGTH_BYTES, mMaxPacketLengthBytes);
+        proto.write(ContextHubInfoProto.SUPPORTS_RELIABLE_MESSAGES,
+                mSupportsReliableMessages);
     }
 
     @Override
@@ -339,6 +361,8 @@
                     && (other.getSleepPowerDrawMw() == mSleepPowerDrawMw)
                     && (other.getPeakPowerDrawMw() == mPeakPowerDrawMw)
                     && (other.getMaxPacketLengthBytes() == mMaxPacketLengthBytes)
+                    && (!Flags.reliableMessage()
+                            || (other.supportsReliableMessages() == mSupportsReliableMessages))
                     && Arrays.equals(other.getSupportedSensors(), mSupportedSensors)
                     && Arrays.equals(other.getMemoryRegions(), mMemoryRegions);
         }
@@ -367,6 +391,7 @@
         mSupportedSensors = new int[numSupportedSensors];
         in.readIntArray(mSupportedSensors);
         mMemoryRegions = in.createTypedArray(MemoryRegion.CREATOR);
+        mSupportsReliableMessages = in.readBoolean();
     }
 
     public int describeContents() {
@@ -393,6 +418,7 @@
         out.writeInt(mSupportedSensors.length);
         out.writeIntArray(mSupportedSensors);
         out.writeTypedArray(mMemoryRegions, flags);
+        out.writeBoolean(mSupportsReliableMessages);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ContextHubInfo> CREATOR
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 481ec72..3a58993 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -29,15 +29,15 @@
 import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.app.PendingIntent;
+import android.chre.flags.Flags;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.hardware.contexthub.ErrorCode;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceManager.ServiceNotFoundException;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -456,32 +456,6 @@
         }
     }
 
-    /**
-     * Helper function to generate a stub for a non-query transaction callback.
-     *
-     * @param transaction the transaction to unblock when complete
-     *
-     * @return the callback
-     *
-     * @hide
-     */
-    private IContextHubTransactionCallback createTransactionCallback(
-            ContextHubTransaction<Void> transaction) {
-        return new IContextHubTransactionCallback.Stub() {
-            @Override
-            public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
-                Log.e(TAG, "Received a query callback on a non-query request");
-                transaction.setResponse(new ContextHubTransaction.Response<Void>(
-                        ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
-            }
-
-            @Override
-            public void onTransactionComplete(int result) {
-                transaction.setResponse(new ContextHubTransaction.Response<Void>(result, null));
-            }
-        };
-    }
-
    /**
     * Helper function to generate a stub for a query transaction callback.
     *
@@ -532,7 +506,8 @@
 
         ContextHubTransaction<Void> transaction =
                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_LOAD_NANOAPP);
-        IContextHubTransactionCallback callback = createTransactionCallback(transaction);
+        IContextHubTransactionCallback callback =
+                ContextHubTransactionHelper.createTransactionCallback(transaction);
 
         try {
             mService.loadNanoAppOnHub(hubInfo.getId(), callback, appBinary);
@@ -560,7 +535,8 @@
 
         ContextHubTransaction<Void> transaction =
                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_UNLOAD_NANOAPP);
-        IContextHubTransactionCallback callback = createTransactionCallback(transaction);
+        IContextHubTransactionCallback callback =
+                ContextHubTransactionHelper.createTransactionCallback(transaction);
 
         try {
             mService.unloadNanoAppFromHub(hubInfo.getId(), callback, nanoAppId);
@@ -588,7 +564,8 @@
 
         ContextHubTransaction<Void> transaction =
                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_ENABLE_NANOAPP);
-        IContextHubTransactionCallback callback = createTransactionCallback(transaction);
+        IContextHubTransactionCallback callback =
+                ContextHubTransactionHelper.createTransactionCallback(transaction);
 
         try {
             mService.enableNanoApp(hubInfo.getId(), callback, nanoAppId);
@@ -616,7 +593,8 @@
 
         ContextHubTransaction<Void> transaction =
                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_DISABLE_NANOAPP);
-        IContextHubTransactionCallback callback = createTransactionCallback(transaction);
+        IContextHubTransactionCallback callback =
+                ContextHubTransactionHelper.createTransactionCallback(transaction);
 
         try {
             mService.disableNanoApp(hubInfo.getId(), callback, nanoAppId);
@@ -732,7 +710,14 @@
                 executor.execute(
                         () -> {
                             callback.onMessageFromNanoApp(client, message);
-                            client.callbackFinished();
+                            if (Flags.reliableMessage()
+                                        && Flags.reliableMessageImplementation()
+                                        && message.isReliable()) {
+                                client.reliableMessageCallbackFinished(
+                                        message.getMessageSequenceNumber(), ErrorCode.OK);
+                            } else {
+                                client.callbackFinished();
+                            }
                         });
             }
 
diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java
index d11e0a9..4060f4c 100644
--- a/core/java/android/hardware/location/ContextHubTransaction.java
+++ b/core/java/android/hardware/location/ContextHubTransaction.java
@@ -16,9 +16,11 @@
 package android.hardware.location;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.chre.flags.Flags;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 
@@ -57,7 +59,8 @@
             TYPE_UNLOAD_NANOAPP,
             TYPE_ENABLE_NANOAPP,
             TYPE_DISABLE_NANOAPP,
-            TYPE_QUERY_NANOAPPS
+            TYPE_QUERY_NANOAPPS,
+            TYPE_RELIABLE_MESSAGE,
     })
     public @interface Type { }
 
@@ -66,6 +69,8 @@
     public static final int TYPE_ENABLE_NANOAPP = 2;
     public static final int TYPE_DISABLE_NANOAPP = 3;
     public static final int TYPE_QUERY_NANOAPPS = 4;
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public static final int TYPE_RELIABLE_MESSAGE = 5;
 
     /**
      * Constants describing the result of a transaction or request through the Context Hub Service.
@@ -81,7 +86,8 @@
             RESULT_FAILED_AT_HUB,
             RESULT_FAILED_TIMEOUT,
             RESULT_FAILED_SERVICE_INTERNAL_FAILURE,
-            RESULT_FAILED_HAL_UNAVAILABLE
+            RESULT_FAILED_HAL_UNAVAILABLE,
+            RESULT_FAILED_NOT_SUPPORTED,
     })
     public @interface Result {}
     public static final int RESULT_SUCCESS = 0;
@@ -117,6 +123,11 @@
      * Failure mode when the Context Hub HAL was not available.
      */
     public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8;
+    /**
+     * Failure mode when the operation is not supported.
+     */
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public static final int RESULT_FAILED_NOT_SUPPORTED = 9;
 
     /**
      * A class describing the response for a ContextHubTransaction.
@@ -221,6 +232,11 @@
                 return upperCase ? "Disable" : "disable";
             case ContextHubTransaction.TYPE_QUERY_NANOAPPS:
                 return upperCase ? "Query" : "query";
+            case ContextHubTransaction.TYPE_RELIABLE_MESSAGE: {
+                if (Flags.reliableMessage()) {
+                    return upperCase ? "Reliable Message" : "reliable message";
+                }
+            }
             default:
                 return upperCase ? "Unknown" : "unknown";
         }
diff --git a/core/java/android/hardware/location/ContextHubTransactionHelper.java b/core/java/android/hardware/location/ContextHubTransactionHelper.java
new file mode 100644
index 0000000..66c03f4
--- /dev/null
+++ b/core/java/android/hardware/location/ContextHubTransactionHelper.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 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 android.hardware.location;
+
+import android.annotation.NonNull;
+import android.util.Log;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Helper class to generate {@link IContextHubTransactionCallback}.
+ *
+ * @hide
+ */
+public class ContextHubTransactionHelper {
+    private static final String TAG = "ContextHubTransactionHelper";
+
+    /**
+     * Helper to generate a stub for a query nanoapp transaction callback.
+     *
+     * @param transaction the transaction to unblock when complete
+     * @return the callback
+     * @hide
+     */
+    public static IContextHubTransactionCallback createNanoAppQueryCallback(
+            @NonNull() ContextHubTransaction<List<NanoAppState>> transaction) {
+        Objects.requireNonNull(transaction, "transaction cannot be null");
+        return new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
+                transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
+                        result, nanoappList));
+            }
+
+            @Override
+            public void onTransactionComplete(int result) {
+                Log.e(TAG, "Received a non-query callback on a query request");
+                transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
+                        ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
+            }
+        };
+    }
+
+    /**
+     * Helper to generate a stub for a non-query transaction callback.
+     *
+     * @param transaction the transaction to unblock when complete
+     * @return the callback
+     * @hide
+     */
+    public static IContextHubTransactionCallback createTransactionCallback(
+            @NonNull() ContextHubTransaction<Void> transaction) {
+        Objects.requireNonNull(transaction, "transaction cannot be null");
+        return new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
+                Log.e(TAG, "Received a query callback on a non-query request");
+                transaction.setResponse(new ContextHubTransaction.Response<Void>(
+                        ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
+            }
+
+            @Override
+            public void onTransactionComplete(int result) {
+                transaction.setResponse(new ContextHubTransaction.Response<Void>(result, null));
+            }
+        };
+    }
+
+}
diff --git a/core/java/android/hardware/location/IContextHubClient.aidl b/core/java/android/hardware/location/IContextHubClient.aidl
index 1ee342e9..ca23705 100644
--- a/core/java/android/hardware/location/IContextHubClient.aidl
+++ b/core/java/android/hardware/location/IContextHubClient.aidl
@@ -18,21 +18,35 @@
 
 import android.app.PendingIntent;
 import android.hardware.location.NanoAppMessage;
+import android.hardware.location.IContextHubTransactionCallback;
 
 /**
  * @hide
  */
 interface IContextHubClient {
-
-    // Sends a message to a nanoapp
+    // Sends a message to a nanoapp.
     int sendMessageToNanoApp(in NanoAppMessage message);
 
-    // Closes the connection with the Context Hub
+    // Closes the connection with the Context Hub.
     void close();
 
     // Returns the unique ID for this client.
     int getId();
 
-    // Notify direct-call message callback completed
+    // Notify the framework that a client callback has finished executing.
     void callbackFinished();
+
+    // Notify the framework that a reliable message client callback has
+    // finished executing.
+    void reliableMessageCallbackFinished(int messageSequenceNumber, byte errorCode);
+
+    /**
+     * Sends a reliable message to a nanoapp.
+     *
+     * @param message The message to send.
+     * @param transactionCallback The transaction callback for reliable message.
+     */
+    int sendReliableMessageToNanoApp(
+        in NanoAppMessage message,
+        in IContextHubTransactionCallback transactionCallback);
 }
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index 7ac1dd1..48aa1bd 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -15,9 +15,11 @@
  */
 package android.hardware.location;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.chre.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -39,13 +41,17 @@
     private int mMessageType;
     private byte[] mMessageBody;
     private boolean mIsBroadcasted;
+    private boolean mIsReliable;
+    private int mMessageSequenceNumber;
 
-    private NanoAppMessage(
-            long nanoAppId, int messageType, byte[] messageBody, boolean broadcasted) {
+    private NanoAppMessage(long nanoAppId, int messageType, byte[] messageBody,
+            boolean broadcasted, boolean isReliable, int messageSequenceNumber) {
         mNanoAppId = nanoAppId;
         mMessageType = messageType;
         mMessageBody = messageBody;
         mIsBroadcasted = broadcasted;
+        mIsReliable = isReliable;
+        mMessageSequenceNumber = messageSequenceNumber;
     }
 
     /**
@@ -62,10 +68,10 @@
      *
      * @return the NanoAppMessage object
      */
-    public static NanoAppMessage createMessageToNanoApp(
-            long targetNanoAppId, int messageType, byte[] messageBody) {
-        return new NanoAppMessage(
-                targetNanoAppId, messageType, messageBody, false /* broadcasted */);
+    public static NanoAppMessage createMessageToNanoApp(long targetNanoAppId, int messageType,
+            byte[] messageBody) {
+        return new NanoAppMessage(targetNanoAppId, messageType, messageBody,
+                false /* broadcasted */, false /* isReliable */, 0 /* messageSequenceNumber */);
     }
 
     /**
@@ -81,9 +87,33 @@
      *
      * @return the NanoAppMessage object
      */
-    public static NanoAppMessage createMessageFromNanoApp(
-            long sourceNanoAppId, int messageType, byte[] messageBody, boolean broadcasted) {
-        return new NanoAppMessage(sourceNanoAppId, messageType, messageBody, broadcasted);
+    public static NanoAppMessage createMessageFromNanoApp(long sourceNanoAppId, int messageType,
+            byte[] messageBody, boolean broadcasted) {
+        return new NanoAppMessage(sourceNanoAppId, messageType, messageBody, broadcasted,
+                false /* isReliable */, 0 /* messageSequenceNumber */);
+    }
+
+    /**
+     * Creates a NanoAppMessage object sent from a nanoapp.
+     *
+     * This factory method is intended only to be used by the Context Hub Service when delivering
+     * messages from a nanoapp to clients.
+     *
+     * @param sourceNanoAppId the ID of the nanoapp that the message was sent from
+     * @param messageType the nanoapp-dependent message type
+     * @param messageBody the byte array message contents
+     * @param broadcasted {@code true} if the message was broadcasted, {@code false} otherwise
+     * @param isReliable if the NanoAppMessage is reliable
+     * @param messageSequenceNumber the message sequence number of the NanoAppMessage
+     *
+     * @return the NanoAppMessage object
+     */
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public static @NonNull NanoAppMessage createMessageFromNanoApp(long sourceNanoAppId,
+            int messageType, @NonNull byte[] messageBody, boolean broadcasted, boolean isReliable,
+            int messageSequenceNumber) {
+        return new NanoAppMessage(sourceNanoAppId, messageType, messageBody, broadcasted,
+                isReliable, messageSequenceNumber);
     }
 
     /**
@@ -114,6 +144,40 @@
         return mIsBroadcasted;
     }
 
+    /**
+     * Returns if the message is reliable. The default value is {@code false}
+     * @return {@code true} if the message is reliable, {@code false} otherwise
+     */
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public boolean isReliable() {
+        return mIsReliable;
+    }
+
+    /**
+     * Returns the message sequence number. The default value is 0
+     * @return the message sequence number of the message
+     */
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public int getMessageSequenceNumber() {
+        return mMessageSequenceNumber;
+    }
+
+    /**
+     * Sets the isReliable field of the message
+     */
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public void setIsReliable(boolean isReliable) {
+        mIsReliable = isReliable;
+    }
+
+    /**
+     * Sets the message sequence number of the message
+     */
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public void setMessageSequenceNumber(int messageSequenceNumber) {
+        mMessageSequenceNumber = messageSequenceNumber;
+    }
+
     private NanoAppMessage(Parcel in) {
         mNanoAppId = in.readLong();
         mIsBroadcasted = (in.readInt() == 1);
@@ -122,6 +186,9 @@
         int msgSize = in.readInt();
         mMessageBody = new byte[msgSize];
         in.readByteArray(mMessageBody);
+
+        mIsReliable = (in.readInt() == 1);
+        mMessageSequenceNumber = in.readInt();
     }
 
     @Override
@@ -137,6 +204,9 @@
 
         out.writeInt(mMessageBody.length);
         out.writeByteArray(mMessageBody);
+
+        out.writeInt(mIsReliable ? 1 : 0);
+        out.writeInt(mMessageSequenceNumber);
     }
 
     public static final @NonNull Creator<NanoAppMessage> CREATOR =
@@ -159,7 +229,9 @@
 
         String ret = "NanoAppMessage[type = " + mMessageType + ", length = " + mMessageBody.length
                 + " bytes, " + (mIsBroadcasted ? "broadcast" : "unicast") + ", nanoapp = 0x"
-                + Long.toHexString(mNanoAppId) + "](";
+                + Long.toHexString(mNanoAppId) + ", isReliable = "
+                + (mIsReliable ? "true" : "false") + ", messageSequenceNumber = "
+                + mMessageSequenceNumber + "](";
         if (length > 0) {
             ret += "data = 0x";
         }
@@ -190,7 +262,11 @@
             isEqual = (other.getNanoAppId() == mNanoAppId)
                     && (other.getMessageType() == mMessageType)
                     && (other.isBroadcastMessage() == mIsBroadcasted)
-                    && Arrays.equals(other.getMessageBody(), mMessageBody);
+                    && Arrays.equals(other.getMessageBody(), mMessageBody)
+                    && (!Flags.reliableMessage()
+                            || (other.isReliable() == mIsReliable))
+                    && (!Flags.reliableMessage()
+                            || (other.getMessageSequenceNumber() == mMessageSequenceNumber));
         }
 
         return isEqual;
diff --git a/core/proto/android/hardware/location/context_hub_info.proto b/core/proto/android/hardware/location/context_hub_info.proto
index de5cd55..95b5a1a 100644
--- a/core/proto/android/hardware/location/context_hub_info.proto
+++ b/core/proto/android/hardware/location/context_hub_info.proto
@@ -46,4 +46,6 @@
     optional float peak_power_draw_mw = 12;
     // The maximum number of bytes that can be sent per message to the hub
     optional int32 max_packet_length_bytes = 13;
+    // Whether reliable messages are supported
+    optional int32 supports_reliable_messages = 14;
 }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index 5c1897d..fc99471 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -25,12 +25,15 @@
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
+import android.chre.flags.Flags;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.content.Context;
 import android.content.Intent;
+import android.hardware.contexthub.ErrorCode;
 import android.hardware.contexthub.HostEndpointInfo;
+import android.hardware.contexthub.MessageDeliveryStatus;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubManager;
 import android.hardware.location.ContextHubTransaction;
@@ -65,6 +68,7 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -441,9 +445,35 @@
     @ContextHubTransaction.Result
     @Override
     public int sendMessageToNanoApp(NanoAppMessage message) {
+        return doSendMessageToNanoApp(message, /* transactionCallback= */ null);
+    }
+
+    /**
+     * Sends a reliable message rom this client to a nanoapp.
+     *
+     * @param message the message to send
+     * @param transactionCallback The callback to use to confirm the delivery of the message for
+     *        reliable messages.
+     * @return the error code of sending the message
+     * @throws SecurityException if this client doesn't have permissions to send a message to the
+     * nanoapp
+     */
+    @ContextHubTransaction.Result
+    @Override
+    public int sendReliableMessageToNanoApp(NanoAppMessage message,
+            IContextHubTransactionCallback transactionCallback) {
+        return doSendMessageToNanoApp(message, transactionCallback);
+    }
+
+    /**
+     * See sendReliableMessageToNanoApp().
+     */
+    @ContextHubTransaction.Result
+    private int doSendMessageToNanoApp(NanoAppMessage message,
+            @Nullable IContextHubTransactionCallback transactionCallback) {
         ContextHubServiceUtil.checkPermissions(mContext);
 
-        int result;
+        @ContextHubTransaction.Result int result;
         if (isRegistered()) {
             int authState = mMessageChannelNanoappIdMap.getOrDefault(
                     message.getNanoAppId(), AUTHORIZATION_UNKNOWN);
@@ -462,13 +492,30 @@
                 checkNanoappPermsAsync();
             }
 
-            try {
-                result = mContextHubProxy.sendMessageToContextHub(
-                    mHostEndPointId, mAttachedContextHubInfo.getId(), message);
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
-                        + mAttachedContextHubInfo.getId() + ")", e);
-                result = ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+            if (!Flags.reliableMessageImplementation() || transactionCallback == null) {
+                try {
+                    result = mContextHubProxy.sendMessageToContextHub(mHostEndPointId,
+                            mAttachedContextHubInfo.getId(), message);
+                } catch (RemoteException e) {
+                    Log.e(TAG,
+                            "RemoteException in sendMessageToNanoApp (target hub ID = "
+                                    + mAttachedContextHubInfo.getId() + ")",
+                            e);
+                    result = ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+                }
+            } else {
+                result = ContextHubTransaction.RESULT_SUCCESS;
+                ContextHubServiceTransaction transaction =
+                        mTransactionManager.createMessageTransaction(mHostEndPointId,
+                                mAttachedContextHubInfo.getId(), message, transactionCallback,
+                                getPackageName());
+                try {
+                    mTransactionManager.addTransaction(transaction);
+                } catch (IllegalStateException e) {
+                    Log.e(TAG, "Unable to add a transaction in sendMessageToNanoApp "
+                            + "(target hub ID = " + mAttachedContextHubInfo.getId() + ")", e);
+                    result = ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE;
+                }
             }
 
             ContextHubEventLogger.getInstance().logMessageToNanoapp(
@@ -569,15 +616,17 @@
     }
 
     /**
-     * Sends a message to the client associated with this object.
+     * Sends a message to the client associated with this object. This function will call
+     * onFinishedCallback when the operation is complete if the message is reliable.
      *
      * @param message the message that came from a nanoapp
      * @param nanoappPermissions permissions required to communicate with the nanoapp sending this
      *     message
      * @param messagePermissions permissions required to consume the message being delivered. These
      *     permissions are what will be attributed to the client through noteOp.
+     * @return An error from ErrorCode
      */
-    void sendMessageToClient(
+    byte sendMessageToClient(
             NanoAppMessage message,
             List<String> nanoappPermissions,
             List<String> messagePermissions) {
@@ -592,7 +641,7 @@
         if (authState == AUTHORIZATION_DENIED_GRACE_PERIOD && !messagePermissions.isEmpty()) {
             Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
                     + " in grace period and napp msg has permissions");
-            return;
+            return ErrorCode.PERMISSION_DENIED;
         }
 
         // If in the grace period, don't check permissions state since it'll cause cleanup
@@ -601,15 +650,23 @@
                 || !notePermissions(messagePermissions, RECEIVE_MSG_NOTE + nanoAppId)) {
             Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
                     + " doesn't have permission");
-            return;
+            return ErrorCode.PERMISSION_DENIED;
         }
 
-        invokeCallback(callback -> callback.onMessageFromNanoApp(message));
+        byte errorCode = invokeCallback(callback -> callback.onMessageFromNanoApp(message));
+        if (errorCode != ErrorCode.OK) {
+            return errorCode;
+        }
 
         Supplier<Intent> supplier =
                 () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, nanoAppId)
                         .putExtra(ContextHubManager.EXTRA_MESSAGE, message);
-        sendPendingIntent(supplier, nanoAppId);
+        Consumer<Byte> onFinishedCallback = (Byte error) ->
+                sendMessageDeliveryStatusToContextHub(message.getMessageSequenceNumber(), error);
+        return sendPendingIntent(supplier, nanoAppId,
+                Flags.reliableMessageImplementation() && message.isReliable()
+                        ? onFinishedCallback
+                        : null);
     }
 
     /**
@@ -873,8 +930,9 @@
      * Helper function to invoke a specified client callback, if the connection is open.
      *
      * @param consumer the consumer specifying the callback to invoke
+     * @return the ErrorCode for this operation
      */
-    private synchronized void invokeCallback(CallbackConsumer consumer) {
+    private synchronized byte invokeCallback(CallbackConsumer consumer) {
         if (mContextHubClientCallback != null) {
             try {
                 acquireWakeLock();
@@ -886,8 +944,10 @@
                                 + mHostEndPointId
                                 + ")",
                         e);
+                return ErrorCode.PERMANENT_ERROR;
             }
         }
+        return ErrorCode.OK;
     }
 
     /**
@@ -918,37 +978,81 @@
     }
 
     /**
-     * Sends an intent to any existing PendingIntent
+     * Sends an intent to any existing PendingIntent.
      *
-     * @param supplier method to create the extra Intent
+     * @param supplier method to create the extra Intent.
+     * @return the ErrorCode indicating the status of sending the intent.
+     * ErrorCode.TRANSIENT_ERROR indicates there is no intent.
      */
-    private synchronized void sendPendingIntent(Supplier<Intent> supplier) {
+    private synchronized byte sendPendingIntent(Supplier<Intent> supplier) {
         if (mPendingIntentRequest.hasPendingIntent()) {
-            doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get(), this);
+            return doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get(),
+                    this);
         }
+        return ErrorCode.OK;
     }
 
     /**
-     * Sends an intent to any existing PendingIntent
+     * Sends an intent to any existing PendingIntent.
      *
-     * @param supplier method to create the extra Intent
-     * @param nanoAppId the ID of the nanoapp which this event is for
+     * @param supplier method to create the extra Intent.
+     * @param nanoAppId the ID of the nanoapp which this event is for.
+     * @return the ErrorCode indicating the status of sending the intent.
+     * ErrorCode.TRANSIENT_ERROR indicates there is no intent.
      */
-    private synchronized void sendPendingIntent(Supplier<Intent> supplier, long nanoAppId) {
+    private synchronized byte sendPendingIntent(Supplier<Intent> supplier, long nanoAppId) {
+        return sendPendingIntent(supplier, nanoAppId, null);
+    }
+
+    /**
+     * Sends an intent to any existing PendingIntent. This function will set the onFinishedCallback
+     * to be called when the pending intent is sent or upon a failure.
+     *
+     * @param supplier method to create the extra Intent.
+     * @param nanoAppId the ID of the nanoapp which this event is for.
+     * @param onFinishedCallback the callback called when the operation is finished.
+     * @return the ErrorCode indicating the status of sending the intent.
+     * ErrorCode.TRANSIENT_ERROR indicates there is no intent.
+     */
+    private synchronized byte sendPendingIntent(Supplier<Intent> supplier, long nanoAppId,
+            Consumer<Byte> onFinishedCallback) {
         if (mPendingIntentRequest.hasPendingIntent()
                 && mPendingIntentRequest.getNanoAppId() == nanoAppId) {
-            doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get(), this);
+            ContextHubClientBroker broker = this;
+            PendingIntent.OnFinished onFinished = new PendingIntent.OnFinished() {
+                @Override
+                public void onSendFinished(
+                        PendingIntent pendingIntent,
+                        Intent intent,
+                        int resultCode,
+                        String resultData,
+                        Bundle resultExtras) {
+                    if (onFinishedCallback != null) {
+                        onFinishedCallback.accept(resultCode == 0
+                                ? ErrorCode.OK
+                                : ErrorCode.TRANSIENT_ERROR);
+                    }
+
+                    broker.onSendFinished(pendingIntent, intent, resultCode, resultData,
+                            resultExtras);
+                }
+            };
+
+            return doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get(),
+                    onFinished);
         }
+        return ErrorCode.OK;
     }
 
     /**
-     * Sends a PendingIntent with extra Intent data
+     * Sends a PendingIntent with extra Intent data.
      *
-     * @param pendingIntent the PendingIntent
-     * @param intent the extra Intent data
+     * @param pendingIntent the PendingIntent.
+     * @param intent the extra Intent data.
+     * @return the ErrorCode indicating the status of sending the intent.
      */
     @VisibleForTesting
-    void doSendPendingIntent(
+    byte doSendPendingIntent(
             PendingIntent pendingIntent,
             Intent intent,
             PendingIntent.OnFinished onFinishedCallback) {
@@ -963,6 +1067,7 @@
                     /* handler= */ null,
                     requiredPermission,
                     /* options= */ null);
+            return ErrorCode.OK;
         } catch (PendingIntent.CanceledException e) {
             mIsPendingIntentCancelled.set(true);
             // The PendingIntent is no longer valid
@@ -973,6 +1078,7 @@
                             + mHostEndPointId
                             + ")");
             close();
+            return ErrorCode.PERMANENT_ERROR;
         }
     }
 
@@ -1089,6 +1195,16 @@
         releaseWakeLock();
     }
 
+    /**
+     * Callback that arrives when direct-call message callback delivery completed.
+     * Used for reliable messages.
+     */
+    @Override
+    public void reliableMessageCallbackFinished(int messageSequenceNumber, byte errorCode) {
+        sendMessageDeliveryStatusToContextHub(messageSequenceNumber, errorCode);
+        callbackFinished();
+    }
+
     @Override
     public void onSendFinished(
             PendingIntent pendingIntent,
@@ -1148,4 +1264,18 @@
                     }
                 });
     }
+
+    private void sendMessageDeliveryStatusToContextHub(int messageSequenceNumber, byte errorCode) {
+        if (!Flags.reliableMessageImplementation()) {
+            return;
+        }
+
+        MessageDeliveryStatus status = new MessageDeliveryStatus();
+        status.messageSequenceNumber = messageSequenceNumber;
+        status.errorCode = errorCode;
+        if (mContextHubProxy.sendMessageDeliveryStatusToContextHub(mAttachedContextHubInfo.getId(),
+                status) != ContextHubTransaction.RESULT_SUCCESS) {
+            Log.e(TAG, "Failed to send the reliable message status");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
index 4de7c0c..4636a49 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
@@ -18,7 +18,9 @@
 
 import android.annotation.IntDef;
 import android.app.PendingIntent;
+import android.chre.flags.Flags;
 import android.content.Context;
+import android.hardware.contexthub.ErrorCode;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.IContextHubClient;
 import android.hardware.location.IContextHubClientCallback;
@@ -218,21 +220,27 @@
     /**
      * Handles a message sent from a nanoapp.
      *
-     * @param contextHubId the ID of the hub where the nanoapp sent the message from
+     * @param contextHubId the ID of the hub where the nanoapp sent the message from.
      * @param hostEndpointId The host endpoint ID of the client that this message is for.
-     * @param message the message send by a nanoapp
-     * @param nanoappPermissions the set of permissions the nanoapp holds
+     * @param message the message send by a nanoapp.
+     * @param nanoappPermissions the set of permissions the nanoapp holds.
      * @param messagePermissions the set of permissions that should be used for attributing
-     * permissions when this message is consumed by a client
+     *        permissions when this message is consumed by a client.
+     * @return An error from ErrorCode.
      */
-    /* package */ void onMessageFromNanoApp(
-            int contextHubId, short hostEndpointId, NanoAppMessage message,
-            List<String> nanoappPermissions, List<String> messagePermissions) {
+    /* package */ byte onMessageFromNanoApp(int contextHubId, short hostEndpointId,
+            NanoAppMessage message, List<String> nanoappPermissions,
+            List<String> messagePermissions) {
         if (DEBUG_LOG_ENABLED) {
             Log.v(TAG, "Received " + message);
         }
 
         if (message.isBroadcastMessage()) {
+            if (Flags.reliableMessageImplementation() && message.isReliable()) {
+                Log.e(TAG, "Received reliable broadcast message from " + message.getNanoAppId());
+                return ErrorCode.PERMANENT_ERROR;
+            }
+
             // Broadcast messages shouldn't be sent with any permissions tagged per CHRE API
             // requirements.
             if (!messagePermissions.isEmpty()) {
@@ -240,21 +248,25 @@
                         + message.getNanoAppId());
             }
 
-            ContextHubEventLogger.getInstance().logMessageFromNanoapp(contextHubId, message, true);
+            ContextHubEventLogger.getInstance().logMessageFromNanoapp(contextHubId, message,
+                    /* success= */ true);
             broadcastMessage(contextHubId, message, nanoappPermissions, messagePermissions);
-        } else {
-            ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(hostEndpointId);
-            if (proxy != null) {
-                ContextHubEventLogger.getInstance().logMessageFromNanoapp(contextHubId, message,
-                                                                          true);
-                proxy.sendMessageToClient(message, nanoappPermissions, messagePermissions);
-            } else {
-                ContextHubEventLogger.getInstance().logMessageFromNanoapp(contextHubId, message,
-                                                                          false);
-                Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = "
-                        + hostEndpointId + ")");
-            }
+            return ErrorCode.OK;
         }
+
+        ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(hostEndpointId);
+        if (proxy == null) {
+            ContextHubEventLogger.getInstance().logMessageFromNanoapp(contextHubId, message,
+                    /* success= */ false);
+            Log.e(TAG,
+                    "Cannot send message to unregistered client (host endpoint ID = "
+                            + hostEndpointId + ")");
+            return ErrorCode.DESTINATION_NOT_FOUND;
+        }
+
+        ContextHubEventLogger.getInstance().logMessageFromNanoapp(contextHubId, message,
+                /* success= */ true);
+        return proxy.sendMessageToClient(message, nanoappPermissions, messagePermissions);
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 08cf3f7..e196dee 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -21,6 +21,7 @@
 import android.app.ActivityManager;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
+import android.chre.flags.Flags;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -29,6 +30,8 @@
 import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.SensorPrivacyManagerInternal;
+import android.hardware.contexthub.ErrorCode;
+import android.hardware.contexthub.MessageDeliveryStatus;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubMessage;
 import android.hardware.location.ContextHubTransaction;
@@ -218,6 +221,11 @@
             resetSettings();
             Log.i(TAG, "Finished Context Hub Service restart");
         }
+
+        @Override
+        public void handleMessageDeliveryStatus(MessageDeliveryStatus messageDeliveryStatus) {
+            handleMessageDeliveryStatusCallback(messageDeliveryStatus);
+        }
     }
 
     public ContextHubService(Context context, IContextHubWrapper contextHubWrapper) {
@@ -822,8 +830,8 @@
                         info.getAppId(), msg.getMsgType(), msg.getData());
 
                 IContextHubClient client = mDefaultClientMap.get(contextHubHandle);
-                success = (client.sendMessageToNanoApp(message) ==
-                        ContextHubTransaction.RESULT_SUCCESS);
+                success = client.sendMessageToNanoApp(message)
+                        == ContextHubTransaction.RESULT_SUCCESS;
             } else {
                 Log.e(TAG, "Failed to send nanoapp message - nanoapp with handle "
                         + nanoAppHandle + " does not exist.");
@@ -841,16 +849,33 @@
      * @param message the message contents
      * @param nanoappPermissions the set of permissions the nanoapp holds
      * @param messagePermissions the set of permissions that should be used for attributing
-     *     permissions when this message is consumed by a client
+     *        permissions when this message is consumed by a client
      */
-    private void handleClientMessageCallback(
-            int contextHubId,
-            short hostEndpointId,
-            NanoAppMessage message,
-            List<String> nanoappPermissions,
+    private void handleClientMessageCallback(int contextHubId, short hostEndpointId,
+            NanoAppMessage message, List<String> nanoappPermissions,
             List<String> messagePermissions) {
-        mClientManager.onMessageFromNanoApp(
-                contextHubId, hostEndpointId, message, nanoappPermissions, messagePermissions);
+        byte errorCode = mClientManager.onMessageFromNanoApp(contextHubId, hostEndpointId, message,
+                nanoappPermissions, messagePermissions);
+        if (message.isReliable() && errorCode != ErrorCode.OK) {
+            sendMessageDeliveryStatusToContextHub(contextHubId, message.getMessageSequenceNumber(),
+                    errorCode);
+        }
+    }
+
+    private void sendMessageDeliveryStatusToContextHub(int contextHubId,
+            int messageSequenceNumber, byte errorCode) {
+        if (!Flags.reliableMessageImplementation()) {
+            return;
+        }
+
+        MessageDeliveryStatus status = new MessageDeliveryStatus();
+        status.messageSequenceNumber = messageSequenceNumber;
+        status.errorCode = errorCode;
+        if (mContextHubWrapper.sendMessageDeliveryStatusToContextHub(contextHubId, status)
+                != ContextHubTransaction.RESULT_SUCCESS) {
+            Log.e(TAG, "Failed to send the reliable message status for message sequence number: "
+                    + messageSequenceNumber + " with error code: " + errorCode);
+        }
     }
 
     /**
@@ -897,6 +922,16 @@
     }
 
     /**
+     * Handles a message deliveyr status from a Context Hub.
+     *
+     * @param messageDeliveryStatus     The message delivery status to deliver.
+     */
+    private void handleMessageDeliveryStatusCallback(MessageDeliveryStatus messageDeliveryStatus) {
+        mTransactionManager.onMessageDeliveryResponse(messageDeliveryStatus.messageSequenceNumber,
+                messageDeliveryStatus.errorCode == ErrorCode.OK);
+    }
+
+    /**
      * Handles an asynchronous event from a Context Hub.
      *
      * @param contextHubId the ID of the hub the response came from
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
index a31aecb..4ee2e99 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
@@ -32,15 +32,20 @@
     @ContextHubTransaction.Type
     private final int mTransactionType;
 
-    /* The ID of the nanoapp this transaction is targeted for, null if not applicable. */
+    /** The ID of the nanoapp this transaction is targeted for, null if not applicable. */
     private final Long mNanoAppId;
 
-    /*
+    /**
      * The host package associated with this transaction.
      */
     private final String mPackage;
 
-    /*
+    /**
+     * The message sequence number associated with this transaction, null if not applicable.
+     */
+    private final Integer mMessageSequenceNumber;
+
+    /**
      * true if the transaction has already completed, false otherwise
      */
     private boolean mIsComplete = false;
@@ -50,6 +55,7 @@
         mTransactionType = type;
         mNanoAppId = null;
         mPackage = packageName;
+        mMessageSequenceNumber = null;
     }
 
     /* package */ ContextHubServiceTransaction(int id, int type, long nanoAppId,
@@ -58,6 +64,16 @@
         mTransactionType = type;
         mNanoAppId = nanoAppId;
         mPackage = packageName;
+        mMessageSequenceNumber = null;
+    }
+
+    /* package */ ContextHubServiceTransaction(int id, int type, String packageName,
+            int messageSequenceNumber) {
+        mTransactionId = id;
+        mTransactionType = type;
+        mNanoAppId = null;
+        mPackage = packageName;
+        mMessageSequenceNumber = messageSequenceNumber;
     }
 
     /**
@@ -111,6 +127,13 @@
     }
 
     /**
+     * @return the message sequence number of this transaction
+     */
+    Integer getMessageSequenceNumber() {
+        return mMessageSequenceNumber;
+    }
+
+    /**
      * Gets the timeout period as defined in IContexthub.hal
      *
      * @return the timeout of this transaction in the specified time unit
@@ -119,6 +142,8 @@
         switch (mTransactionType) {
             case ContextHubTransaction.TYPE_LOAD_NANOAPP:
                 return unit.convert(30L, TimeUnit.SECONDS);
+            case ContextHubTransaction.TYPE_RELIABLE_MESSAGE:
+                return unit.convert(1000L, TimeUnit.MILLISECONDS);
             case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
             case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
             case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
@@ -152,7 +177,11 @@
         if (mNanoAppId != null) {
             out += "appId = 0x" + Long.toHexString(mNanoAppId) + ", ";
         }
-        out += "package = " + mPackage + ")";
+        out += "package = " + mPackage;
+        if (mMessageSequenceNumber != null) {
+            out += ", messageSequenceNumber = " + mMessageSequenceNumber;
+        }
+        out += ")";
 
         return out;
     }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index f637149..33d2ff0 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -276,6 +276,8 @@
         aidlMessage.messageBody = message.getMessageBody();
         // This explicit definition is required to avoid erroneous behavior at the binder.
         aidlMessage.permissions = new String[0];
+        aidlMessage.isReliable = message.isReliable();
+        aidlMessage.messageSequenceNumber = message.getMessageSequenceNumber();
 
         return aidlMessage;
     }
@@ -306,7 +308,8 @@
             android.hardware.contexthub.ContextHubMessage message) {
         return NanoAppMessage.createMessageFromNanoApp(
                 message.nanoappId, message.messageType, message.messageBody,
-                message.hostEndPoint == HOST_ENDPOINT_BROADCAST);
+                message.hostEndPoint == HOST_ENDPOINT_BROADCAST,
+                message.isReliable, message.messageSequenceNumber);
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index e46b8c0c..b18871c 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -19,6 +19,7 @@
 import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.IContextHubTransactionCallback;
 import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppMessage;
 import android.hardware.location.NanoAppState;
 import android.os.RemoteException;
 import android.util.Log;
@@ -75,6 +76,11 @@
      */
     private final AtomicInteger mNextAvailableId = new AtomicInteger();
 
+    /**
+     * The next available message sequence number
+     */
+    private final AtomicInteger mNextAvailableMessageSequenceNumber = new AtomicInteger();
+
     /*
      * An executor and the future object for scheduling timeout timers
      */
@@ -309,6 +315,47 @@
     }
 
     /**
+     * Creates a transaction to send a reliable message.
+     *
+     * @param hostEndpointId      The ID of the host endpoint sending the message.
+     * @param contextHubId        The ID of the hub to send the message to.
+     * @param message             The message to send.
+     * @param transactionCallback The callback of the transactions.
+     * @param packageName         The host package associated with this transaction.
+     * @return The generated transaction.
+     */
+    /* package */ ContextHubServiceTransaction createMessageTransaction(
+            short hostEndpointId, int contextHubId, NanoAppMessage message,
+            IContextHubTransactionCallback transactionCallback, String packageName) {
+        return new ContextHubServiceTransaction(mNextAvailableId.getAndIncrement(),
+                ContextHubTransaction.TYPE_RELIABLE_MESSAGE, packageName,
+                mNextAvailableMessageSequenceNumber.getAndIncrement()) {
+            @Override
+            /* package */ int onTransact() {
+                try {
+                    message.setIsReliable(/* isReliable= */ true);
+                    message.setMessageSequenceNumber(getMessageSequenceNumber());
+
+                    return mContextHubProxy.sendMessageToContextHub(hostEndpointId, contextHubId,
+                            message);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while trying to send a reliable message", e);
+                    return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+                }
+            }
+
+            @Override
+            /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
+                try {
+                    transactionCallback.onTransactionComplete(result);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while calling client onTransactionComplete", e);
+                }
+            }
+        };
+    }
+
+    /**
      * Creates a transaction for querying for a list of nanoapps.
      *
      * @param contextHubId       the ID of the hub to query
@@ -397,6 +444,30 @@
         removeTransactionAndStartNext();
     }
 
+    /* package */
+    synchronized void onMessageDeliveryResponse(int messageSequenceNumber, boolean success) {
+        ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+        if (transaction == null) {
+            Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
+            return;
+        }
+
+        Integer transactionMessageSequenceNumber = transaction.getMessageSequenceNumber();
+        if (transaction.getTransactionType() != ContextHubTransaction.TYPE_RELIABLE_MESSAGE
+                || transactionMessageSequenceNumber == null
+                || transactionMessageSequenceNumber != messageSequenceNumber) {
+            Log.w(TAG, "Received unexpected message transaction response (expected message "
+                    + "sequence number = "
+                    + transaction.getMessageSequenceNumber()
+                    + ", received messageSequenceNumber = " + messageSequenceNumber + ")");
+            return;
+        }
+
+        transaction.onTransactionComplete(success ? ContextHubTransaction.RESULT_SUCCESS :
+                        ContextHubTransaction.RESULT_FAILED_AT_HUB);
+        removeTransactionAndStartNext();
+    }
+
     /**
      * Handles a query response from a Context Hub.
      *
@@ -481,10 +552,10 @@
                     }
                 };
 
-                long timeoutSeconds = transaction.getTimeout(TimeUnit.SECONDS);
+                long timeoutMs = transaction.getTimeout(TimeUnit.MILLISECONDS);
                 try {
-                    mTimeoutFuture = mTimeoutExecutor.schedule(onTimeoutFunc, timeoutSeconds,
-                        TimeUnit.SECONDS);
+                    mTimeoutFuture = mTimeoutExecutor.schedule(
+                            onTimeoutFunc, timeoutMs, TimeUnit.MILLISECONDS);
                 } catch (Exception e) {
                     Log.e(TAG, "Error when schedule a timer", e);
                 }
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 9c27c22..552809b 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.chre.flags.Flags;
 import android.hardware.contexthub.HostEndpointInfo;
 import android.hardware.contexthub.MessageDeliveryStatus;
 import android.hardware.contexthub.NanSessionRequest;
@@ -88,18 +89,25 @@
         /**
          * Handles a message from a nanoapp to a ContextHubClient.
          *
-         * @param hostEndpointId     The host endpoint ID of the recipient.
-         * @param message            The message from the nanoapp.
-         * @param nanoappPermissions The list of permissions held by the nanoapp.
-         * @param messagePermissions The list of permissions required to receive the message.
+         * @param hostEndpointId            The host endpoint ID of the recipient.
+         * @param message                   The message from the nanoapp.
+         * @param nanoappPermissions        The list of permissions held by the nanoapp.
+         * @param messagePermissions        The list of permissions required to receive the message.
          */
         void handleNanoappMessage(short hostEndpointId, NanoAppMessage message,
                 List<String> nanoappPermissions, List<String> messagePermissions);
 
         /**
-         * Handles a restart of the service
+         * Handles a restart of the service.
          */
         void handleServiceRestart();
+
+        /**
+         * Handles a message delivery status.
+         *
+         * @param messageDeliveryStatus     The message delivery status to deliver.
+         */
+        void handleMessageDeliveryStatus(MessageDeliveryStatus messageDeliveryStatus);
     }
 
     /**
@@ -308,15 +316,25 @@
     /**
      * Sends a message to the Context Hub.
      *
-     * @param hostEndpointId The host endpoint ID of the sender.
-     * @param contextHubId   The ID of the Context Hub to send the message to.
-     * @param message        The message to send.
+     * @param hostEndpointId         The host endpoint ID of the sender.
+     * @param contextHubId           The ID of the Context Hub to send the message to.
+     * @param message                The message to send.
      * @return the result of the message sending.
      */
     @ContextHubTransaction.Result
-    public abstract int sendMessageToContextHub(
-            short hostEndpointId, int contextHubId, NanoAppMessage message)
-            throws RemoteException;
+    public abstract int sendMessageToContextHub(short hostEndpointId, int contextHubId,
+            NanoAppMessage message) throws RemoteException;
+
+    /**
+     * Sends a transaction status to the Context Hub.
+     *
+     * @param contextHubId The ID of the context hub to sent the status to.
+     * @param status       The status of the transaction.
+     * @return the result of the message sending.
+     */
+    @ContextHubTransaction.Result
+    public abstract int sendMessageDeliveryStatusToContextHub(
+            int contextHubId, MessageDeliveryStatus status);
 
     /**
      * Loads a nanoapp on the Context Hub.
@@ -443,8 +461,7 @@
             public void handleContextHubMessage(android.hardware.contexthub.ContextHubMessage msg,
                     String[] msgContentPerms) {
                 mHandler.post(() -> {
-                    mCallback.handleNanoappMessage(
-                            (short) msg.hostEndPoint,
+                    mCallback.handleNanoappMessage((short) msg.hostEndPoint,
                             ContextHubServiceUtil.createNanoAppMessage(msg),
                             new ArrayList<>(Arrays.asList(msg.permissions)),
                             new ArrayList<>(Arrays.asList(msgContentPerms)));
@@ -468,9 +485,17 @@
                 // TODO(271471342): Implement
             }
 
-            public void handleMessageDeliveryStatus(char hostEndPointId,
+            public void handleMessageDeliveryStatus(
+                    char hostEndpointId,
                     MessageDeliveryStatus messageDeliveryStatus) {
-                // TODO(b/312417087): Implement reliable message support
+                if (Flags.reliableMessageImplementation()) {
+                    mHandler.post(() -> {
+                        mCallback.handleMessageDeliveryStatus(messageDeliveryStatus);
+                    });
+                } else {
+                    Log.w(TAG, "handleMessageDeliveryStatus called when the "
+                            + "reliableMessageImplementation flag is disabled");
+                }
             }
 
             public byte[] getUuid() {
@@ -624,17 +649,35 @@
         }
 
         @ContextHubTransaction.Result
-        public int sendMessageToContextHub(
-                short hostEndpointId, int contextHubId, NanoAppMessage message)
-                throws RemoteException {
+        public int sendMessageToContextHub(short hostEndpointId, int contextHubId,
+                NanoAppMessage message) throws RemoteException {
             android.hardware.contexthub.IContextHub hub = getHub();
             if (hub == null) {
                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
             }
 
             try {
-                hub.sendMessageToHub(contextHubId,
-                        ContextHubServiceUtil.createAidlContextHubMessage(hostEndpointId, message));
+                var msg = ContextHubServiceUtil.createAidlContextHubMessage(
+                        hostEndpointId, message);
+                hub.sendMessageToHub(contextHubId, msg);
+                return ContextHubTransaction.RESULT_SUCCESS;
+            } catch (RemoteException | ServiceSpecificException e) {
+                return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+            } catch (IllegalArgumentException e) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+        }
+
+        @ContextHubTransaction.Result
+        public int sendMessageDeliveryStatusToContextHub(int contextHubId,
+                MessageDeliveryStatus status) {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+
+            try {
+                hub.sendMessageDeliveryStatusToHub(contextHubId, status);
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -847,8 +890,7 @@
 
             @Override
             public void handleClientMsg(ContextHubMsg message) {
-                mCallback.handleNanoappMessage(
-                        message.hostEndPoint,
+                mCallback.handleNanoappMessage(message.hostEndPoint,
                         ContextHubServiceUtil.createNanoAppMessage(message),
                         Collections.emptyList() /* nanoappPermissions */,
                         Collections.emptyList() /* messagePermissions */);
@@ -880,8 +922,7 @@
             @Override
             public void handleClientMsg_1_2(android.hardware.contexthub.V1_2.ContextHubMsg message,
                     ArrayList<String> messagePermissions) {
-                mCallback.handleNanoappMessage(
-                        message.msg_1_0.hostEndPoint,
+                mCallback.handleNanoappMessage(message.msg_1_0.hostEndPoint,
                         ContextHubServiceUtil.createNanoAppMessage(message.msg_1_0),
                         message.permissions, messagePermissions);
             }
@@ -899,9 +940,12 @@
         }
 
         @ContextHubTransaction.Result
-        public int sendMessageToContextHub(
-                short hostEndpointId, int contextHubId, NanoAppMessage message)
-                throws RemoteException {
+        public int sendMessageToContextHub(short hostEndpointId, int contextHubId,
+                NanoAppMessage message) throws RemoteException {
+            if (message.isReliable()) {
+                Log.e(TAG, "Reliable messages are only supported with the AIDL HAL");
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
             ContextHubMsg messageToNanoApp =
                     ContextHubServiceUtil.createHidlContextHubMessage(hostEndpointId, message);
             return ContextHubServiceUtil.toTransactionResult(
@@ -909,6 +953,13 @@
         }
 
         @ContextHubTransaction.Result
+        public int sendMessageDeliveryStatusToContextHub(int contextHubId,
+                MessageDeliveryStatus status) {
+            // Only supported on the AIDL implementation.
+            return ContextHubTransaction.RESULT_FAILED_NOT_SUPPORTED;
+        }
+
+        @ContextHubTransaction.Result
         public int loadNanoapp(int contextHubId, NanoAppBinary binary,
                 int transactionId) throws RemoteException {
             android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =