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 =