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

Commit e891c195 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes Iee6060cd,Iad2185bd,Ifc2a1b67 into main

* changes:
  Check noteOp when receiving messages from endpoints
  Post endpoint callback in async thread
  Implements permission handling for endpoint messages
parents 6137a48e 804d8a6b
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -358,6 +358,7 @@ public class HubEndpoint {
                    service.registerEndpoint(
                            mPendingHubEndpointInfo,
                            mServiceCallback,
                            mPendingHubEndpointInfo.getName(),
                            mPendingHubEndpointInfo.getTag());
            mAssignedHubEndpointInfo = serviceToken.getAssignedHubEndpointInfo();
            mServiceToken = serviceToken;
@@ -514,6 +515,7 @@ public class HubEndpoint {
        /** Create a builder for {@link HubEndpoint} */
        public Builder(@NonNull Context context) {
            mPackageName = context.getPackageName();
            mTag = context.getAttributionTag();
            mVersion = (int) context.getApplicationInfo().longVersionCode;
            mMainExecutor = context.getMainExecutor();
        }
@@ -532,6 +534,7 @@ public class HubEndpoint {
        /**
         * Set a tag string. The tag can be used to further identify the creator of the endpoint.
         * Endpoints created by the same package share the same name but should have different tags.
         * The default value of the tag is retrieved from {@link Context#getAttributionTag()}.
         */
        @NonNull
        public Builder setTag(@NonNull String tag) {
+1 −1
Original line number Diff line number Diff line
@@ -137,7 +137,7 @@ interface IContextHubService {

    // Register an endpoint with the context hub
    @EnforcePermission("ACCESS_CONTEXT_HUB")
    IContextHubEndpoint registerEndpoint(in HubEndpointInfo pendingEndpointInfo, in IContextHubEndpointCallback callback, String packageName);
    IContextHubEndpoint registerEndpoint(in HubEndpointInfo pendingEndpointInfo, in IContextHubEndpointCallback callback, String packageName, String attributionTag);

    // Register an endpoint discovery callback (id)
    @EnforcePermission("ACCESS_CONTEXT_HUB")
+238 −46
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.location.contexthub;

import android.app.AppOpsManager;
import android.content.Context;
import android.hardware.contexthub.EndpointInfo;
import android.hardware.contexthub.ErrorCode;
@@ -26,16 +27,18 @@ import android.hardware.contexthub.IContextHubEndpointCallback;
import android.hardware.contexthub.IEndpointCommunication;
import android.hardware.contexthub.Message;
import android.hardware.contexthub.MessageDeliveryStatus;
import android.hardware.contexthub.Reason;
import android.hardware.location.ContextHubTransaction;
import android.hardware.location.IContextHubTransactionCallback;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;

import java.util.HashSet;
import java.util.Set;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;

/**
@@ -45,9 +48,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
 * @hide
 */
public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
        implements IBinder.DeathRecipient {
        implements IBinder.DeathRecipient, AppOpsManager.OnOpChangedListener {
    private static final String TAG = "ContextHubEndpointBroker";

    /** Message used by noteOp when this client receives a message from an endpoint. */
    private static final String RECEIVE_MSG_NOTE = "ContextHubEndpointMessageDelivery";

    /** The context of the service. */
    private final Context mContext;

@@ -57,6 +63,9 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
    /** The manager that registered this endpoint. */
    private final ContextHubEndpointManager mEndpointManager;

    /** Manager used for noting permissions usage of this broker. */
    private final AppOpsManager mAppOpsManager;

    /** Metadata about this endpoint (app-facing container). */
    private final HubEndpointInfo mEndpointInfo;

@@ -71,24 +80,60 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub

    private final Object mOpenSessionLock = new Object();

    /** The set of session IDs that are pending remote acceptance */
    @GuardedBy("mOpenSessionLock")
    private final Set<Integer> mPendingSessionIds = new HashSet<>();
    static class SessionInfo {
        enum SessionState {
            /* The session is pending acceptance from the remote endpoint. */
            PENDING,
            /* The session is active and can transport messages. */
            ACTIVE,
        };

    /** The set of session IDs that are actively enabled by this endpoint */
    @GuardedBy("mOpenSessionLock")
    private final Set<Integer> mActiveSessionIds = new HashSet<>();
        private final HubEndpointInfo mRemoteEndpointInfo;

        private SessionState mSessionState = SessionState.PENDING;

        private final boolean mRemoteInitiated;

        SessionInfo(HubEndpointInfo remoteEndpointInfo, boolean remoteInitiated) {
            mRemoteEndpointInfo = remoteEndpointInfo;
            mRemoteInitiated = remoteInitiated;
        }

        public boolean isRemoteInitiated() {
            return mRemoteInitiated;
        }

        public HubEndpointInfo getRemoteEndpointInfo() {
            return mRemoteEndpointInfo;
        }

    /** The set of session IDs that are actively enabled by the remote endpoint */
        public void setSessionState(SessionState state) {
            mSessionState = state;
        }

        public boolean isActive() {
            return mSessionState == SessionState.ACTIVE;
        }
    }

    /** A map between a session ID which maps to its current state. */
    @GuardedBy("mOpenSessionLock")
    private final Set<Integer> mActiveRemoteSessionIds = new HashSet<>();
    private final SparseArray<SessionInfo> mSessionInfoMap = new SparseArray<>();

    /** The package name of the app that created the endpoint */
    private final String mPackageName;

    /* Transaction manager used for sending reliable messages */
    /** The attribution tag of the module that created the endpoint */
    private final String mAttributionTag;

    /** Transaction manager used for sending reliable messages */
    private final ContextHubTransactionManager mTransactionManager;

    /** The PID/UID of the endpoint package providing IContextHubEndpointCallback */
    private final int mPid;

    private final int mUid;

    /* package */ ContextHubEndpointBroker(
            Context context,
            IEndpointCommunication hubInterface,
@@ -96,6 +141,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
            EndpointInfo halEndpointInfo,
            IContextHubEndpointCallback callback,
            String packageName,
            String attributionTag,
            ContextHubTransactionManager transactionManager) {
        mContext = context;
        mHubInterface = hubInterface;
@@ -104,7 +150,14 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
        mHalEndpointInfo = halEndpointInfo;
        mContextHubEndpointCallback = callback;
        mPackageName = packageName;
        mAttributionTag = attributionTag;
        mTransactionManager = transactionManager;

        mPid = Binder.getCallingPid();
        mUid = Binder.getCallingUid();

        mAppOpsManager = context.getSystemService(AppOpsManager.class);
        mAppOpsManager.startWatchingMode(AppOpsManager.OP_NONE, mPackageName, this);
    }

    @Override
@@ -118,12 +171,17 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
            throws RemoteException {
        super.openSession_enforcePermission();
        if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
        if (!hasEndpointPermissions(destination)) {
            throw new SecurityException(
                    "Insufficient permission to open a session with endpoint: " + destination);
        }

        int sessionId = mEndpointManager.reserveSessionId();
        EndpointInfo halEndpointInfo = ContextHubServiceUtil.convertHalEndpointInfo(destination);

        synchronized (mOpenSessionLock) {
            try {
                mPendingSessionIds.add(sessionId);
                mSessionInfoMap.put(sessionId, new SessionInfo(destination, false));
                mHubInterface.openEndpointSession(
                        sessionId,
                        halEndpointInfo.id,
@@ -131,8 +189,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
                        serviceDescriptor);
            } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
                Log.e(TAG, "Exception while calling HAL openEndpointSession", e);
                mPendingSessionIds.remove(sessionId);
                mEndpointManager.returnSessionId(sessionId);
                cleanupSessionResources(sessionId);
                throw e;
            }

@@ -145,13 +202,11 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
    public void closeSession(int sessionId, int reason) throws RemoteException {
        super.closeSession_enforcePermission();
        if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
        try {
            mHubInterface.closeEndpointSession(
                    sessionId, ContextHubServiceUtil.toHalReason(reason));
        } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
            Log.e(TAG, "Exception while calling HAL closeEndpointSession", e);
            throw e;
        if (!cleanupSessionResources(sessionId)) {
            throw new IllegalArgumentException(
                    "Unknown session ID in closeSession: id=" + sessionId);
        }
        halCloseEndpointSession(sessionId, ContextHubServiceUtil.toHalReason(reason));
    }

    @Override
@@ -165,15 +220,11 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
            Log.e(TAG, "RemoteException while calling HAL unregisterEndpoint", e);
        }
        synchronized (mOpenSessionLock) {
            for (int id : mPendingSessionIds) {
                mEndpointManager.returnSessionId(id);
            // Iterate in reverse since cleanupSessionResources will remove the entry
            for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
                int id = mSessionInfoMap.keyAt(i);
                cleanupSessionResources(id);
            }
            for (int id : mActiveSessionIds) {
                mEndpointManager.returnSessionId(id);
            }
            mPendingSessionIds.clear();
            mActiveSessionIds.clear();
            mActiveRemoteSessionIds.clear();
        }
        mEndpointManager.unregisterEndpoint(mEndpointInfo.getIdentifier().getEndpoint());
    }
@@ -183,9 +234,14 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
    public void openSessionRequestComplete(int sessionId) {
        super.openSessionRequestComplete_enforcePermission();
        synchronized (mOpenSessionLock) {
            SessionInfo info = mSessionInfoMap.get(sessionId);
            if (info == null) {
                throw new IllegalArgumentException(
                        "openSessionRequestComplete for invalid session id=" + sessionId);
            }
            try {
                mHubInterface.endpointSessionOpenComplete(sessionId);
                mActiveRemoteSessionIds.add(sessionId);
                info.setSessionState(SessionInfo.SessionState.ACTIVE);
            } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
                Log.e(TAG, "Exception while calling endpointSessionOpenComplete", e);
            }
@@ -198,15 +254,11 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
            int sessionId, HubMessage message, IContextHubTransactionCallback callback) {
        super.sendMessage_enforcePermission();
        Message halMessage = ContextHubServiceUtil.createHalMessage(message);
        synchronized (mOpenSessionLock) {
            if (!mActiveSessionIds.contains(sessionId)
                    && !mActiveRemoteSessionIds.contains(sessionId)) {
        if (!isSessionActive(sessionId)) {
            throw new SecurityException(
                    "sendMessage called on inactive session (id= " + sessionId + ")");
        }
        }

        // TODO(b/381102453): Handle permissions
        if (callback == null) {
            try {
                mHubInterface.sendMessageToEndpoint(sessionId, halMessage);
@@ -258,6 +310,31 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
        }
    }

    @Override
    public void onOpChanged(String op, String packageName) {
        if (!packageName.equals(mPackageName)) {
            Log.w(
                    TAG,
                    "onOpChanged called with invalid package "
                            + packageName
                            + " expected "
                            + mPackageName);
        } else {
            synchronized (mOpenSessionLock) {
                // Iterate in reverse since cleanupSessionResources will remove the entry
                for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
                    int id = mSessionInfoMap.keyAt(i);
                    HubEndpointInfo target = mSessionInfoMap.get(id).getRemoteEndpointInfo();
                    if (!hasEndpointPermissions(target)) {
                        halCloseEndpointSessionNoThrow(id, Reason.PERMISSION_DENIED);
                        onCloseEndpointSession(id, Reason.PERMISSION_DENIED);
                        // Resource cleanup is done in onCloseEndpointSession
                    }
                }
            }
        }
    }

    /* package */ void attachDeathRecipient() throws RemoteException {
        if (mContextHubEndpointCallback != null) {
            mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */);
@@ -266,21 +343,36 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub

    /* package */ void onEndpointSessionOpenRequest(
            int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
        if (!hasEndpointPermissions(initiator)) {
            halCloseEndpointSessionNoThrow(sessionId, Reason.PERMISSION_DENIED);
            return;
        }

        synchronized (mOpenSessionLock) {
            if (hasSessionId(sessionId)) {
                Log.e(TAG, "Existing session in onEndpointSessionOpenRequest: id=" + sessionId);
                halCloseEndpointSessionNoThrow(sessionId, Reason.UNSPECIFIED);
                return;
            }
            mSessionInfoMap.put(sessionId, new SessionInfo(initiator, true));
        }

        if (mContextHubEndpointCallback != null) {
            try {
                mContextHubEndpointCallback.onSessionOpenRequest(
                        sessionId, initiator, serviceDescriptor);
            } catch (RemoteException e) {
                Log.e(TAG, "RemoteException while calling onSessionOpenRequest", e);
                cleanupSessionResources(sessionId);
                return;
            }
        }
    }

    /* package */ void onCloseEndpointSession(int sessionId, byte reason) {
        synchronized (mOpenSessionLock) {
            mPendingSessionIds.remove(sessionId);
            mActiveSessionIds.remove(sessionId);
            mActiveRemoteSessionIds.remove(sessionId);
        if (!cleanupSessionResources(sessionId)) {
            Log.w(TAG, "Unknown session ID in onCloseEndpointSession: id=" + sessionId);
            return;
        }
        if (mContextHubEndpointCallback != null) {
            try {
@@ -294,8 +386,11 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub

    /* package */ void onEndpointSessionOpenComplete(int sessionId) {
        synchronized (mOpenSessionLock) {
            mPendingSessionIds.remove(sessionId);
            mActiveSessionIds.add(sessionId);
            if (!hasSessionId(sessionId)) {
                Log.w(TAG, "Unknown session ID in onEndpointSessionOpenComplete: id=" + sessionId);
                return;
            }
            mSessionInfoMap.get(sessionId).setSessionState(SessionInfo.SessionState.ACTIVE);
        }
        if (mContextHubEndpointCallback != null) {
            try {
@@ -307,11 +402,51 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
    }

    /* package */ void onMessageReceived(int sessionId, HubMessage message) {
        HubEndpointInfo remote;
        synchronized (mOpenSessionLock) {
            if (!isSessionActive(sessionId)) {
                Log.e(
                        TAG,
                        "Dropping message for inactive session (id="
                                + sessionId
                                + ") with message: "
                                + message);
                sendMessageDeliveryStatus(
                        sessionId, message.getMessageSequenceNumber(), ErrorCode.PERMANENT_ERROR);
                return;
            }
            remote = mSessionInfoMap.get(sessionId).getRemoteEndpointInfo();
        }
        if (!ContextHubServiceUtil.notePermissions(
                mAppOpsManager,
                mUid,
                mPackageName,
                mAttributionTag,
                remote.getRequiredPermissions(),
                RECEIVE_MSG_NOTE
                        + "-0x"
                        + Long.toHexString(remote.getIdentifier().getHub())
                        + "-0x"
                        + Long.toHexString(remote.getIdentifier().getEndpoint()))) {
            Log.e(
                    TAG,
                    "Dropping message from "
                            + remote
                            + ". "
                            + mPackageName
                            + " doesn't have permission");
            sendMessageDeliveryStatus(
                    sessionId, message.getMessageSequenceNumber(), ErrorCode.PERMISSION_DENIED);
            return;
        }

        if (mContextHubEndpointCallback != null) {
            try {
                mContextHubEndpointCallback.onMessageReceived(sessionId, message);
            } catch (RemoteException e) {
                Log.e(TAG, "RemoteException while calling onMessageReceived", e);
                sendMessageDeliveryStatus(
                        sessionId, message.getMessageSequenceNumber(), ErrorCode.TRANSIENT_ERROR);
            }
        }
    }
@@ -323,9 +458,66 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub

    /* package */ boolean hasSessionId(int sessionId) {
        synchronized (mOpenSessionLock) {
            return mPendingSessionIds.contains(sessionId)
                    || mActiveSessionIds.contains(sessionId)
                    || mActiveRemoteSessionIds.contains(sessionId);
            return mSessionInfoMap.contains(sessionId);
        }
    }

    /**
     * Calls the HAL closeEndpointSession API.
     *
     * @param sessionId The session ID to close
     * @param halReason The HAL reason
     */
    private void halCloseEndpointSession(int sessionId, byte halReason) throws RemoteException {
        try {
            mHubInterface.closeEndpointSession(sessionId, halReason);
        } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
            throw e;
        }
    }

    /** Same as halCloseEndpointSession but does not throw the exception */
    private void halCloseEndpointSessionNoThrow(int sessionId, byte halReason) {
        try {
            halCloseEndpointSession(sessionId, halReason);
        } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
            Log.e(TAG, "Exception while calling HAL closeEndpointSession", e);
        }
    }

    /**
     * Cleans up resources related to a session with the provided ID.
     *
     * @param sessionId The session ID to clean up resources for
     * @return false if the session ID was invalid
     */
    private boolean cleanupSessionResources(int sessionId) {
        synchronized (mOpenSessionLock) {
            SessionInfo info = mSessionInfoMap.get(sessionId);
            if (info != null && !info.isRemoteInitiated()) {
                mEndpointManager.returnSessionId(sessionId);
                mSessionInfoMap.remove(sessionId);
            }
            return info != null;
        }
    }

    /**
     * @param sessionId The ID of the session to check
     * @return true if the session with the given ID is currently active
     */
    private boolean isSessionActive(int sessionId) {
        synchronized (mOpenSessionLock) {
            return hasSessionId(sessionId) && mSessionInfoMap.get(sessionId).isActive();
        }
    }

    /**
     * @param targetEndpointInfo The target endpoint to check permissions for
     * @return true if this endpoint has sufficient permission to the provided target endpoint
     */
    private boolean hasEndpointPermissions(HubEndpointInfo targetEndpointInfo) {
        Collection<String> requiredPermissions = targetEndpointInfo.getRequiredPermissions();
        return ContextHubServiceUtil.hasPermissions(mContext, mPid, mUid, requiredPermissions);
    }
}
+3 −1
Original line number Diff line number Diff line
@@ -191,7 +191,8 @@ import java.util.concurrent.ConcurrentHashMap;
    /* package */ IContextHubEndpoint registerEndpoint(
            HubEndpointInfo pendingEndpointInfo,
            IContextHubEndpointCallback callback,
            String packageName)
            String packageName,
            String attributionTag)
            throws RemoteException {
        if (!mSessionIdsValid) {
            throw new IllegalStateException("ContextHubEndpointManager failed to initialize");
@@ -215,6 +216,7 @@ import java.util.concurrent.ConcurrentHashMap;
                        halEndpointInfo,
                        callback,
                        packageName,
                        attributionTag,
                        mTransactionManager);
        mEndpointMap.put(endpointId, broker);

+33 −14
Original line number Diff line number Diff line
@@ -21,6 +21,9 @@ import android.hardware.contexthub.HubMessage;
import android.hardware.contexthub.IEndpointCallback;
import android.hardware.contexthub.Message;
import android.hardware.contexthub.MessageDeliveryStatus;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.os.RemoteException;

/** IEndpointCallback implementation. */
@@ -29,6 +32,11 @@ public class ContextHubHalEndpointCallback
    private final IEndpointLifecycleCallback mEndpointLifecycleCallback;
    private final IEndpointSessionCallback mEndpointSessionCallback;

    // Use this thread in case where the execution requires to be on an async service thread.
    private final HandlerThread mHandlerThread =
            new HandlerThread("Context Hub endpoint callback", Process.THREAD_PRIORITY_BACKGROUND);
    private Handler mHandler;

    /** Interface for listening for endpoint start and stop events. */
    public interface IEndpointLifecycleCallback {
        /** Called when a batch of endpoints started. */
@@ -65,6 +73,9 @@ public class ContextHubHalEndpointCallback
            IEndpointSessionCallback endpointSessionCallback) {
        mEndpointLifecycleCallback = endpointLifecycleCallback;
        mEndpointSessionCallback = endpointSessionCallback;

        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
    }

    @Override
@@ -77,7 +88,7 @@ public class ContextHubHalEndpointCallback
        for (int i = 0; i < halEndpointInfos.length; i++) {
            endpointInfos[i] = new HubEndpointInfo(halEndpointInfos[i]);
        }
        mEndpointLifecycleCallback.onEndpointStarted(endpointInfos);
        mHandler.post(() -> mEndpointLifecycleCallback.onEndpointStarted(endpointInfos));
    }

    @Override
@@ -87,40 +98,48 @@ public class ContextHubHalEndpointCallback
        for (int i = 0; i < halEndpointIds.length; i++) {
            endpointIds[i] = new HubEndpointInfo.HubEndpointIdentifier(halEndpointIds[i]);
        }
        mEndpointLifecycleCallback.onEndpointStopped(endpointIds, reason);
        mHandler.post(() -> mEndpointLifecycleCallback.onEndpointStopped(endpointIds, reason));
    }

    @Override
    public void onEndpointSessionOpenRequest(
            int i, EndpointId destination, EndpointId initiator, String s) throws RemoteException {
            int sessionId, EndpointId destination, EndpointId initiator, String serviceDescriptor)
            throws RemoteException {
        HubEndpointInfo.HubEndpointIdentifier destinationId =
                new HubEndpointInfo.HubEndpointIdentifier(destination.hubId, destination.id);
        HubEndpointInfo.HubEndpointIdentifier initiatorId =
                new HubEndpointInfo.HubEndpointIdentifier(initiator.hubId, initiator.id);
        mEndpointSessionCallback.onEndpointSessionOpenRequest(i, destinationId, initiatorId, s);
        mHandler.post(
                () ->
                        mEndpointSessionCallback.onEndpointSessionOpenRequest(
                                sessionId, destinationId, initiatorId, serviceDescriptor));
    }

    @Override
    public void onCloseEndpointSession(int i, byte b) throws RemoteException {
        mEndpointSessionCallback.onCloseEndpointSession(i, b);
    public void onCloseEndpointSession(int sessionId, byte reason) throws RemoteException {
        mHandler.post(() -> mEndpointSessionCallback.onCloseEndpointSession(sessionId, reason));
    }

    @Override
    public void onEndpointSessionOpenComplete(int i) throws RemoteException {
        mEndpointSessionCallback.onEndpointSessionOpenComplete(i);
    public void onEndpointSessionOpenComplete(int sessionId) throws RemoteException {
        mHandler.post(() -> mEndpointSessionCallback.onEndpointSessionOpenComplete(sessionId));
    }

    @Override
    public void onMessageReceived(int i, Message message) throws RemoteException {
    public void onMessageReceived(int sessionId, Message message) throws RemoteException {
        HubMessage hubMessage = ContextHubServiceUtil.createHubMessage(message);
        mEndpointSessionCallback.onMessageReceived(i, hubMessage);
        mHandler.post(() -> mEndpointSessionCallback.onMessageReceived(sessionId, hubMessage));
    }

    @Override
    public void onMessageDeliveryStatusReceived(int i, MessageDeliveryStatus messageDeliveryStatus)
            throws RemoteException {
    public void onMessageDeliveryStatusReceived(
            int sessionId, MessageDeliveryStatus messageDeliveryStatus) throws RemoteException {
        mHandler.post(
                () ->
                        mEndpointSessionCallback.onMessageDeliveryStatusReceived(
                i, messageDeliveryStatus.messageSequenceNumber, messageDeliveryStatus.errorCode);
                                sessionId,
                                messageDeliveryStatus.messageSequenceNumber,
                                messageDeliveryStatus.errorCode));
    }

    @Override
Loading