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

Commit c74b4ff4 authored by Arthur Ishiguro's avatar Arthur Ishiguro Committed by Yifei Zhang
Browse files

contexthub: create basic endpoint session message API

- Create a basic parcelable for HubMessage.
- Create interface definitions for bi-directional messaging in IContextHubEndpoint and IContextHubEndpointCallback.
- Connect callbacks for messages that requires ACK.

Test: manual
API-Coverage-Bug: 377554469
Bug: 375487784
Flag: android.chre.flags.offload_api
Change-Id: I231ee01335a93469cbdbc1d986ce017be75a6d23
parent d5080aa6
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -5224,6 +5224,7 @@ package android.hardware.contexthub {
  @FlaggedApi("android.chre.flags.offload_api") public class HubEndpoint {
    method @Nullable public android.hardware.contexthub.IHubEndpointLifecycleCallback getLifecycleCallback();
    method @Nullable public android.hardware.contexthub.IHubEndpointMessageCallback getMessageCallback();
    method @Nullable public String getTag();
  }
@@ -5232,6 +5233,8 @@ package android.hardware.contexthub {
    method @NonNull public android.hardware.contexthub.HubEndpoint build();
    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback);
    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback);
    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull android.hardware.contexthub.IHubEndpointMessageCallback);
    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointMessageCallback);
    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setTag(@NonNull String);
  }
@@ -5251,6 +5254,7 @@ package android.hardware.contexthub {
  @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable {
    method public void close();
    method @NonNull public android.hardware.location.ContextHubTransaction<java.lang.Void> sendMessage(@NonNull android.hardware.contexthub.HubMessage);
  }
  @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSessionResult {
@@ -5260,6 +5264,22 @@ package android.hardware.contexthub {
    method @NonNull public static android.hardware.contexthub.HubEndpointSessionResult reject(@NonNull String);
  }
  @FlaggedApi("android.chre.flags.offload_api") public final class HubMessage implements android.os.Parcelable {
    method @NonNull public static android.hardware.contexthub.HubMessage createMessage(int, @NonNull byte[]);
    method @NonNull public static android.hardware.contexthub.HubMessage createMessage(int, @NonNull byte[], @NonNull android.hardware.contexthub.HubMessage.DeliveryParams);
    method public int describeContents();
    method @NonNull public byte[] getMessageBody();
    method public int getMessageType();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.contexthub.HubMessage> CREATOR;
  }
  public static class HubMessage.DeliveryParams {
    method public boolean isResponseRequired();
    method @NonNull public static android.hardware.contexthub.HubMessage.DeliveryParams makeBasic();
    method @NonNull public android.hardware.contexthub.HubMessage.DeliveryParams setResponseRequired(boolean);
  }
  @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
    method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
    method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo);
@@ -5269,6 +5289,10 @@ package android.hardware.contexthub {
    field public static final int REASON_UNSPECIFIED = 0; // 0x0
  }
  @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointMessageCallback {
    method public void onMessageReceived(@NonNull android.hardware.contexthub.HubEndpointSession, @NonNull android.hardware.contexthub.HubMessage);
  }
}
package android.hardware.devicestate {
@@ -6335,6 +6359,8 @@ package android.hardware.location {
    field public static final int RESULT_SUCCESS = 0; // 0x0
    field public static final int TYPE_DISABLE_NANOAPP = 3; // 0x3
    field public static final int TYPE_ENABLE_NANOAPP = 2; // 0x2
    field @FlaggedApi("android.chre.flags.offload_api") public static final int TYPE_HUB_MESSAGE_DEFAULT = 6; // 0x6
    field @FlaggedApi("android.chre.flags.offload_api") public static final int TYPE_HUB_MESSAGE_REQUIRES_RESPONSE = 7; // 0x7
    field public static final int TYPE_LOAD_NANOAPP = 0; // 0x0
    field public static final int TYPE_QUERY_NANOAPPS = 4; // 0x4
    field public static final int TYPE_RELIABLE_MESSAGE = 5; // 0x5
+103 −2
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.annotation.SystemApi;
import android.chre.flags.Flags;
import android.content.Context;
import android.hardware.location.IContextHubService;
import android.hardware.location.IContextHubTransactionCallback;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
@@ -46,7 +47,9 @@ public class HubEndpoint {
    private final Object mLock = new Object();
    private final HubEndpointInfo mPendingHubEndpointInfo;
    @Nullable private final IHubEndpointLifecycleCallback mLifecycleCallback;
    @Nullable private final IHubEndpointMessageCallback mMessageCallback;
    @NonNull private final Executor mLifecycleCallbackExecutor;
    @NonNull private final Executor mMessageCallbackExecutor;

    @GuardedBy("mLock")
    private final SparseArray<HubEndpointSession> mActiveSessions = new SparseArray<>();
@@ -216,6 +219,50 @@ public class HubEndpoint {
                                });
                    }
                }

                @Override
                public void onMessageReceived(int sessionId, HubMessage message)
                        throws RemoteException {
                    final HubEndpointSession activeSession;

                    // Retrieve the active session
                    synchronized (mLock) {
                        activeSession = mActiveSessions.get(sessionId);
                    }
                    if (activeSession == null) {
                        Log.i(TAG, "onMessageReceived: session not active, id=" + sessionId);
                    }

                    if (activeSession == null || mMessageCallback == null) {
                        if (message.getDeliveryParams().isResponseRequired()) {
                            try {
                                mServiceToken.sendMessageDeliveryStatus(
                                        sessionId,
                                        message.getMessageSequenceNumber(),
                                        ErrorCode.DESTINATION_NOT_FOUND);
                            } catch (RemoteException e) {
                                e.rethrowFromSystemServer();
                            }
                        }
                        return;
                    }

                    // Execute the callback
                    mMessageCallbackExecutor.execute(
                            () -> {
                                mMessageCallback.onMessageReceived(activeSession, message);
                                if (message.getDeliveryParams().isResponseRequired()) {
                                    try {
                                        mServiceToken.sendMessageDeliveryStatus(
                                                sessionId,
                                                message.getMessageSequenceNumber(),
                                                ErrorCode.OK);
                                    } catch (RemoteException e) {
                                        e.rethrowFromSystemServer();
                                    }
                                }
                            });
                }
            };

    /** Binder returned from system service, non-null while registered. */
@@ -227,10 +274,15 @@ public class HubEndpoint {
    private HubEndpoint(
            @NonNull HubEndpointInfo pendingEndpointInfo,
            @Nullable IHubEndpointLifecycleCallback endpointLifecycleCallback,
            @NonNull Executor lifecycleCallbackExecutor) {
            @NonNull Executor lifecycleCallbackExecutor,
            @Nullable IHubEndpointMessageCallback endpointMessageCallback,
            @NonNull Executor messageCallbackExecutor) {
        mPendingHubEndpointInfo = pendingEndpointInfo;

        mLifecycleCallback = endpointLifecycleCallback;
        mLifecycleCallbackExecutor = lifecycleCallbackExecutor;
        mMessageCallback = endpointMessageCallback;
        mMessageCallbackExecutor = messageCallbackExecutor;
    }

    /** @hide */
@@ -337,6 +389,24 @@ public class HubEndpoint {
        }
    }

    void sendMessage(
            HubEndpointSession session,
            HubMessage message,
            @Nullable IContextHubTransactionCallback transactionCallback) {
        IContextHubEndpoint serviceToken = mServiceToken;
        if (serviceToken == null) {
            // Not registered
            return;
        }

        try {
            serviceToken.sendMessage(session.getId(), message, transactionCallback);
        } catch (RemoteException e) {
            Log.e(TAG, "sendMessage: failed to send message session=" + session, e);
            e.rethrowFromSystemServer();
        }
    }

    @Nullable
    public String getTag() {
        return mPendingHubEndpointInfo.getTag();
@@ -347,6 +417,11 @@ public class HubEndpoint {
        return mLifecycleCallback;
    }

    @Nullable
    public IHubEndpointMessageCallback getMessageCallback() {
        return mMessageCallback;
    }

    /** Builder for a {@link HubEndpoint} object. */
    public static final class Builder {
        private final String mPackageName;
@@ -355,12 +430,16 @@ public class HubEndpoint {

        @NonNull private Executor mLifecycleCallbackExecutor;

        @Nullable private IHubEndpointMessageCallback mMessageCallback;
        @NonNull private Executor mMessageCallbackExecutor;

        @Nullable private String mTag;

        /** Create a builder for {@link HubEndpoint} */
        public Builder(@NonNull Context context) {
            mPackageName = context.getPackageName();
            mLifecycleCallbackExecutor = context.getMainExecutor();
            mMessageCallbackExecutor = context.getMainExecutor();
        }

        /**
@@ -394,13 +473,35 @@ public class HubEndpoint {
            return this;
        }

        /** Attach a callback interface for message events for this Endpoint */
        @NonNull
        public Builder setMessageCallback(@NonNull IHubEndpointMessageCallback messageCallback) {
            mMessageCallback = messageCallback;
            return this;
        }

        /**
         * Attach a callback interface for message events for this Endpoint with a specified
         * executor
         */
        @NonNull
        public Builder setMessageCallback(
                @NonNull @CallbackExecutor Executor executor,
                @NonNull IHubEndpointMessageCallback messageCallback) {
            mMessageCallbackExecutor = executor;
            mMessageCallback = messageCallback;
            return this;
        }

        /** Build the {@link HubEndpoint} object. */
        @NonNull
        public HubEndpoint build() {
            return new HubEndpoint(
                    new HubEndpointInfo(mPackageName, mTag),
                    mLifecycleCallback,
                    mLifecycleCallbackExecutor);
                    mLifecycleCallbackExecutor,
                    mMessageCallback,
                    mMessageCallbackExecutor);
        }
    }
}
+39 −3
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@ import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.chre.flags.Flags;
import android.hardware.location.ContextHubTransaction;
import android.hardware.location.ContextHubTransactionHelper;
import android.hardware.location.IContextHubTransactionCallback;
import android.util.CloseGuard;

import java.util.concurrent.atomic.AtomicBoolean;
@@ -27,8 +30,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
/**
 * An object representing a communication session between two different hub endpoints.
 *
 * <p>A published enpoint can receive
 *
 * @hide
 */
@SystemApi
@@ -38,7 +39,6 @@ public class HubEndpointSession implements AutoCloseable {

    private final int mId;

    // TODO(b/377717509): Implement Message sending API & interface
    @NonNull private final HubEndpoint mHubEndpoint;
    @NonNull private final HubEndpointInfo mInitiator;
    @NonNull private final HubEndpointInfo mDestination;
@@ -57,6 +57,42 @@ public class HubEndpointSession implements AutoCloseable {
        mInitiator = initiator;
    }

    /**
     * Send a message to the peer endpoint in this session.
     *
     * @param message The message object constructed with {@link HubMessage#createMessage}.
     * @return For messages that does not require a response, the transaction will immediately
     *     complete. For messages that requires a response, the transaction will complete after
     *     receiving the response for the message.
     */
    @NonNull
    public ContextHubTransaction<Void> sendMessage(@NonNull HubMessage message) {
        if (mIsClosed.get()) {
            throw new IllegalStateException("Session is already closed.");
        }

        boolean isResponseRequired = message.getDeliveryParams().isResponseRequired();
        ContextHubTransaction<Void> ret =
                new ContextHubTransaction<>(
                        isResponseRequired
                                ? ContextHubTransaction.TYPE_HUB_MESSAGE_REQUIRES_RESPONSE
                                : ContextHubTransaction.TYPE_HUB_MESSAGE_DEFAULT);
        if (!isResponseRequired) {
            // If the message doesn't require acknowledgement, respond with success immediately
            // TODO(b/379162322): Improve handling of synchronous failures.
            mHubEndpoint.sendMessage(this, message, null);
            ret.setResponse(
                    new ContextHubTransaction.Response<>(
                            ContextHubTransaction.RESULT_SUCCESS, null));
        } else {
            IContextHubTransactionCallback callback =
                    ContextHubTransactionHelper.createTransactionCallback(ret);
            // Sequence number will be assigned at the service
            mHubEndpoint.sendMessage(this, message, callback);
        }
        return ret;
    }

    /** @hide */
    public int getId() {
        return mId;
+22 −0
Original line number Diff line number Diff line
/*
 * 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.contexthub;

/**
 * @hide
 */
parcelable HubMessage;
+289 −0
Original line number Diff line number Diff line
/*
 * 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.contexthub;

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;

import libcore.util.HexEncoding;

import java.util.Arrays;
import java.util.Objects;

/**
 * A class describing general messages send through the Context Hub Service.
 *
 * @hide
 */
@SystemApi
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
public final class HubMessage implements Parcelable {
    private static final int DEBUG_LOG_NUM_BYTES = 16;

    private final int mMessageType;
    private final byte[] mMessageBody;

    private final DeliveryParams mDeliveryParams;
    private int mMessageSequenceNumber;

    /**
     * Configurable options for message delivery. This option can be passed into {@link
     * HubEndpointSession#sendMessage} to specify the behavior of message delivery.
     */
    public static class DeliveryParams {
        private boolean mResponseRequired;

        private DeliveryParams(boolean responseRequired) {
            mResponseRequired = responseRequired;
        }

        /** Get the acknowledgement requirement. */
        public boolean isResponseRequired() {
            return mResponseRequired;
        }

        /**
         * Set the response requirement for a message. Message sent with this option will have a
         * {@link android.hardware.location.ContextHubTransaction.Response} when the peer received
         * the message. Default is false.
         */
        @NonNull
        public DeliveryParams setResponseRequired(boolean required) {
            mResponseRequired = required;
            return this;
        }

        /** Construct a default delivery option. */
        @NonNull
        public static DeliveryParams makeBasic() {
            return new DeliveryParams(false);
        }

        @Override
        public String toString() {
            StringBuilder out = new StringBuilder();
            out.append("DeliveryParams[");
            out.append("responseRequired = ").append(mResponseRequired);
            out.append("]");
            return out.toString();
        }

        @Override
        public int hashCode() {
            return Objects.hash(mResponseRequired);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }

            if (obj instanceof DeliveryParams other) {
                return other.mResponseRequired == mResponseRequired;
            }

            return false;
        }
    }

    private HubMessage(int messageType, byte[] messageBody, DeliveryParams deliveryParams) {
        mMessageType = messageType;
        mMessageBody = messageBody;
        mDeliveryParams = deliveryParams;
    }

    /**
     * Creates a HubMessage object to send to through an endpoint.
     *
     * @param messageType the endpoint & service dependent message type
     * @param messageBody the byte array message contents
     * @return the HubMessage object
     */
    @NonNull
    public static HubMessage createMessage(int messageType, @NonNull byte[] messageBody) {
        return new HubMessage(messageType, messageBody, DeliveryParams.makeBasic());
    }

    /**
     * Creates a HubMessage object to send to through an endpoint.
     *
     * @param messageType the endpoint & service dependent message type
     * @param messageBody the byte array message contents
     * @param deliveryParams The message delivery parameters. See {@link HubMessage.DeliveryParams}
     *     for more details.
     * @return the HubMessage object
     */
    @NonNull
    public static HubMessage createMessage(
            int messageType, @NonNull byte[] messageBody, @NonNull DeliveryParams deliveryParams) {
        return new HubMessage(messageType, messageBody, deliveryParams);
    }

    /**
     * Retrieve the message type.
     *
     * @return the type of the message
     */
    public int getMessageType() {
        return mMessageType;
    }

    /**
     * Retrieve the body of the message. The body can be an empty byte array.
     *
     * @return the byte array contents of the message
     */
    @NonNull
    public byte[] getMessageBody() {
        return mMessageBody;
    }

    /**
     * Retrieve the {@link DeliveryParams} object specifying the behavior of message delivery.
     *
     * @hide
     */
    public DeliveryParams getDeliveryParams() {
        return mDeliveryParams;
    }

    /**
     * Assign a message sequence number. This should only be called by the system service.
     *
     * @hide
     */
    public void setMessageSequenceNumber(int messageSequenceNumber) {
        mMessageSequenceNumber = messageSequenceNumber;
    }

    /**
     * Returns the message sequence number. The default value is 0.
     *
     * @return the message sequence number of the message
     * @hide
     */
    public int getMessageSequenceNumber() {
        return mMessageSequenceNumber;
    }

    private HubMessage(@NonNull Parcel in) {
        mMessageType = in.readInt();

        int msgSize = in.readInt();
        mMessageBody = new byte[msgSize];
        in.readByteArray(mMessageBody);

        mDeliveryParams = DeliveryParams.makeBasic();
        mDeliveryParams.setResponseRequired(in.readInt() == 1);
        mMessageSequenceNumber = in.readInt();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel out, int flags) {
        out.writeInt(mMessageType);

        out.writeInt(mMessageBody.length);
        out.writeByteArray(mMessageBody);

        out.writeInt(mDeliveryParams.isResponseRequired() ? 1 : 0);
        out.writeInt(mMessageSequenceNumber);
    }

    public static final @NonNull Creator<HubMessage> CREATOR =
            new Creator<>() {
                @Override
                public HubMessage createFromParcel(Parcel in) {
                    return new HubMessage(in);
                }

                @Override
                public HubMessage[] newArray(int size) {
                    return new HubMessage[size];
                }
            };

    @NonNull
    @Override
    public String toString() {
        int length = mMessageBody.length;

        StringBuilder out = new StringBuilder();
        out.append("HubMessage[type = ").append(mMessageType);
        out.append(", length = ").append(mMessageBody.length);
        out.append(", messageSequenceNumber = ").append(mMessageSequenceNumber);
        out.append(", deliveryParams = ").append(mDeliveryParams);
        out.append("](");

        if (length > 0) {
            out.append("data = 0x");
        }
        for (int i = 0; i < Math.min(length, DEBUG_LOG_NUM_BYTES); i++) {
            out.append(HexEncoding.encodeToString(mMessageBody[i], true /* upperCase */));

            if ((i + 1) % 4 == 0) {
                out.append(" ");
            }
        }
        if (length > DEBUG_LOG_NUM_BYTES) {
            out.append("...");
        }
        out.append(")");

        return out.toString();
    }

    @Override
    public boolean equals(@Nullable Object object) {
        if (object == this) {
            return true;
        }

        boolean isEqual = false;
        if (object instanceof HubMessage other) {
            isEqual =
                    (other.getMessageType() == mMessageType)
                            && Arrays.equals(other.getMessageBody(), mMessageBody)
                            && (other.getDeliveryParams().equals(mDeliveryParams))
                            && (other.getMessageSequenceNumber() == mMessageSequenceNumber);
        }

        return isEqual;
    }

    @Override
    public int hashCode() {
        if (!Flags.fixApiCheck()) {
            return super.hashCode();
        }

        return Objects.hash(
                mMessageType,
                Arrays.hashCode(mMessageBody),
                mDeliveryParams,
                mMessageSequenceNumber);
    }
}
Loading