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

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

Merge changes Ib62b350a,Iaf7c650f into main

* changes:
  Close active sessions on endpoint unregistrations
  Implement HAL restart logic for hub endpoints
parents 91dbfc66 54da7075
Loading
Loading
Loading
Loading
+62 −13
Original line number Diff line number Diff line
@@ -43,7 +43,6 @@ import com.android.internal.annotations.GuardedBy;

import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * A class that represents a broker for the endpoint registered by the client app. This class
@@ -89,8 +88,11 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
    /** The remote callback interface for this endpoint. */
    private final IContextHubEndpointCallback mContextHubEndpointCallback;

    /** True if this endpoint is registered with the service. */
    private AtomicBoolean mIsRegistered = new AtomicBoolean(true);
    /** True if this endpoint is registered with the service/HAL. */
    @GuardedBy("mRegistrationLock")
    private boolean mIsRegistered = false;

    private final Object mRegistrationLock = new Object();

    private final Object mOpenSessionLock = new Object();

@@ -192,7 +194,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
    public int openSession(HubEndpointInfo destination, String serviceDescriptor)
            throws RemoteException {
        super.openSession_enforcePermission();
        if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
        if (!isRegistered()) throw new IllegalStateException("Endpoint is not registered");
        if (!hasEndpointPermissions(destination)) {
            throw new SecurityException(
                    "Insufficient permission to open a session with endpoint: " + destination);
@@ -223,7 +225,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
    public void closeSession(int sessionId, int reason) throws RemoteException {
        super.closeSession_enforcePermission();
        if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
        if (!isRegistered()) throw new IllegalStateException("Endpoint is not registered");
        if (!cleanupSessionResources(sessionId)) {
            throw new IllegalArgumentException(
                    "Unknown session ID in closeSession: id=" + sessionId);
@@ -235,19 +237,26 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
    public void unregister() {
        super.unregister_enforcePermission();
        mIsRegistered.set(false);
        try {
            mHubInterface.unregisterEndpoint(mHalEndpointInfo);
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException while calling HAL unregisterEndpoint", e);
        }
        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);
                halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE);
                cleanupSessionResources(id);
            }
        }
        synchronized (mRegistrationLock) {
            if (!isRegistered()) {
                Log.w(TAG, "Attempting to unregister when already unregistered");
                return;
            }
            mIsRegistered = false;
            try {
                mHubInterface.unregisterEndpoint(mHalEndpointInfo);
            } catch (RemoteException e) {
                Log.e(TAG, "RemoteException while calling HAL unregisterEndpoint", e);
            }
        }
        mEndpointManager.unregisterEndpoint(mEndpointInfo.getIdentifier().getEndpoint());
        releaseWakeLockOnExit();
    }
@@ -335,7 +344,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
    /** Invoked when the underlying binder of this broker has died at the client process. */
    @Override
    public void binderDied() {
        if (mIsRegistered.get()) {
        if (isRegistered()) {
            unregister();
        }
    }
@@ -365,6 +374,22 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
        }
    }

    /**
     * Registers this endpoints with the Context Hub HAL.
     *
     * @throws RemoteException if the registrations fails with a RemoteException
     */
    /* package */ void register() throws RemoteException {
        synchronized (mRegistrationLock) {
            if (isRegistered()) {
                Log.w(TAG, "Attempting to register when already registered");
            } else {
                mHubInterface.registerEndpoint(mHalEndpointInfo);
                mIsRegistered = true;
            }
        }
    }

    /* package */ void attachDeathRecipient() throws RemoteException {
        if (mContextHubEndpointCallback != null) {
            mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */);
@@ -425,6 +450,24 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
        }
    }

    /* package */ void onHalRestart() {
        synchronized (mRegistrationLock) {
            mIsRegistered = false;
            try {
                register();
            } catch (RemoteException e) {
                Log.e(TAG, "RemoteException while calling HAL registerEndpoint", e);
            }
        }
        synchronized (mOpenSessionLock) {
            for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
                int id = mSessionInfoMap.keyAt(i);
                onCloseEndpointSession(id, Reason.HUB_RESET);
            }
        }
        // TODO(b/390029594): Cancel any ongoing reliable communication transactions
    }

    private Optional<Byte> onEndpointSessionOpenRequestInternal(
            int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
        if (!hasEndpointPermissions(initiator)) {
@@ -553,7 +596,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
    private void acquireWakeLock() {
        Binder.withCleanCallingIdentity(
                () -> {
                    if (mIsRegistered.get()) {
                    if (isRegistered()) {
                        mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
                    }
                });
@@ -608,4 +651,10 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
        }
        return true;
    }

    private boolean isRegistered() {
        synchronized (mRegistrationLock) {
            return mIsRegistered;
        }
    }
}
+9 −6
Original line number Diff line number Diff line
@@ -206,12 +206,6 @@ import java.util.function.Consumer;
        EndpointInfo halEndpointInfo =
                ContextHubServiceUtil.createHalEndpointInfo(
                        pendingEndpointInfo, endpointId, SERVICE_HUB_ID);
        try {
            mHubInterface.registerEndpoint(halEndpointInfo);
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException while calling HAL registerEndpoint", e);
            throw e;
        }
        broker =
                new ContextHubEndpointBroker(
                        mContext,
@@ -222,6 +216,7 @@ import java.util.function.Consumer;
                        packageName,
                        attributionTag,
                        mTransactionManager);
        broker.register();
        mEndpointMap.put(endpointId, broker);

        try {
@@ -282,6 +277,14 @@ import java.util.function.Consumer;
        mEndpointMap.remove(endpointId);
    }

    /** Invoked by the service when the Context Hub HAL restarts. */
    /* package */ void onHalRestart() {
        for (ContextHubEndpointBroker broker : mEndpointMap.values()) {
            // The broker will close existing sessions and re-register itself
            broker.onHalRestart();
        }
    }

    @Override
    public void onEndpointSessionOpenRequest(
            int sessionId,
+3 −0
Original line number Diff line number Diff line
@@ -259,6 +259,9 @@ public class ContextHubService extends IContextHubService.Stub {
            if (mHubInfoRegistry != null) {
                mHubInfoRegistry.onHalRestart();
            }
            if (mEndpointManager != null) {
                mEndpointManager.onHalRestart();
            }
            resetSettings();
            if (Flags.reconnectHostEndpointsAfterHalRestart()) {
                mClientManager.forEachClientOfHub(mContextHubId,
+111 −17
Original line number Diff line number Diff line
@@ -20,10 +20,12 @@ import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.hardware.contexthub.EndpointInfo;
import android.hardware.contexthub.ErrorCode;
import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier;
@@ -32,6 +34,7 @@ import android.hardware.contexthub.IContextHubEndpoint;
import android.hardware.contexthub.IContextHubEndpointCallback;
import android.hardware.contexthub.IEndpointCommunication;
import android.hardware.contexthub.MessageDeliveryStatus;
import android.hardware.contexthub.Reason;
import android.os.Binder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -61,6 +64,9 @@ public class ContextHubEndpointTest {
    private static final int ENDPOINT_ID = 1;
    private static final String ENDPOINT_PACKAGE_NAME = "com.android.server.location.contexthub";

    private static final String TARGET_ENDPOINT_NAME = "Example target endpoint";
    private static final int TARGET_ENDPOINT_ID = 1;

    private ContextHubClientManager mClientManager;
    private ContextHubEndpointManager mEndpointManager;
    private HubInfoRegistry mHubInfoRegistry;
@@ -95,23 +101,8 @@ public class ContextHubEndpointTest {

    @Test
    public void testRegisterEndpoint() throws RemoteException {
        // Register an endpoint and confirm we can get a valid IContextHubEndoint reference
        HubEndpointInfo info =
                new HubEndpointInfo(
                        ENDPOINT_NAME, ENDPOINT_ID, ENDPOINT_PACKAGE_NAME, Collections.emptyList());
        IContextHubEndpoint endpoint =
                mEndpointManager.registerEndpoint(
                        info, mMockCallback, ENDPOINT_PACKAGE_NAME, /* attributionTag= */ null);
        assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(1);
        assertThat(endpoint).isNotNull();
        HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
        assertThat(assignedInfo).isNotNull();
        HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
        assertThat(assignedIdentifier).isNotNull();

        // Unregister the endpoint and confirm proper clean-up
        mEndpointManager.unregisterEndpoint(assignedIdentifier.getEndpoint());
        assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(0);
        IContextHubEndpoint endpoint = registerExampleEndpoint();
        unregisterExampleEndpoint(endpoint);
    }

    @Test
@@ -146,4 +137,107 @@ public class ContextHubEndpointTest {
        assertThat(statusCaptor.getValue().messageSequenceNumber).isEqualTo(sequenceNumber);
        assertThat(statusCaptor.getValue().errorCode).isEqualTo(ErrorCode.DESTINATION_NOT_FOUND);
    }

    @Test
    public void testHalRestart() throws RemoteException {
        IContextHubEndpoint endpoint = registerExampleEndpoint();

        // Verify that the endpoint is still registered after a HAL restart
        HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
        HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
        mEndpointManager.onHalRestart();
        ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
        verify(mMockEndpointCommunications, times(2)).registerEndpoint(statusCaptor.capture());
        assertThat(statusCaptor.getValue().id.id).isEqualTo(assignedIdentifier.getEndpoint());
        assertThat(statusCaptor.getValue().id.hubId).isEqualTo(assignedIdentifier.getHub());

        unregisterExampleEndpoint(endpoint);
    }

    @Test
    public void testHalRestartOnOpenSession() throws RemoteException {
        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
        IContextHubEndpoint endpoint = registerExampleEndpoint();

        HubEndpointInfo targetInfo =
                new HubEndpointInfo(
                        TARGET_ENDPOINT_NAME,
                        TARGET_ENDPOINT_ID,
                        ENDPOINT_PACKAGE_NAME,
                        Collections.emptyList());
        int sessionId = endpoint.openSession(targetInfo, /* serviceDescriptor= */ null);
        mEndpointManager.onEndpointSessionOpenComplete(sessionId);
        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE - 1);

        mEndpointManager.onHalRestart();

        HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
        HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
        ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
        verify(mMockEndpointCommunications, times(2)).registerEndpoint(statusCaptor.capture());
        assertThat(statusCaptor.getValue().id.id).isEqualTo(assignedIdentifier.getEndpoint());
        assertThat(statusCaptor.getValue().id.hubId).isEqualTo(assignedIdentifier.getHub());

        verify(mMockCallback)
                .onSessionClosed(
                        sessionId, ContextHubServiceUtil.toAppHubEndpointReason(Reason.HUB_RESET));

        unregisterExampleEndpoint(endpoint);
        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
    }

    @Test
    public void testOpenSessionOnUnregistration() throws RemoteException {
        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
        IContextHubEndpoint endpoint = registerExampleEndpoint();

        HubEndpointInfo targetInfo =
                new HubEndpointInfo(
                        TARGET_ENDPOINT_NAME,
                        TARGET_ENDPOINT_ID,
                        ENDPOINT_PACKAGE_NAME,
                        Collections.emptyList());
        int sessionId = endpoint.openSession(targetInfo, /* serviceDescriptor= */ null);
        mEndpointManager.onEndpointSessionOpenComplete(sessionId);
        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE - 1);

        unregisterExampleEndpoint(endpoint);
        verify(mMockEndpointCommunications).closeEndpointSession(sessionId, Reason.ENDPOINT_GONE);
        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
    }

    private IContextHubEndpoint registerExampleEndpoint() throws RemoteException {
        HubEndpointInfo info =
                new HubEndpointInfo(
                        ENDPOINT_NAME, ENDPOINT_ID, ENDPOINT_PACKAGE_NAME, Collections.emptyList());
        IContextHubEndpoint endpoint =
                mEndpointManager.registerEndpoint(
                        info, mMockCallback, ENDPOINT_PACKAGE_NAME, /* attributionTag= */ null);
        assertThat(endpoint).isNotNull();
        HubEndpointInfo assignedInfo = endpoint.getAssignedHubEndpointInfo();
        assertThat(assignedInfo).isNotNull();
        HubEndpointIdentifier assignedIdentifier = assignedInfo.getIdentifier();
        assertThat(assignedIdentifier).isNotNull();

        // Confirm registerEndpoint was called with the right contents
        ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
        verify(mMockEndpointCommunications).registerEndpoint(statusCaptor.capture());
        assertThat(statusCaptor.getValue().id.id).isEqualTo(assignedIdentifier.getEndpoint());
        assertThat(statusCaptor.getValue().id.hubId).isEqualTo(assignedIdentifier.getHub());
        assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(1);

        return endpoint;
    }

    private void unregisterExampleEndpoint(IContextHubEndpoint endpoint) throws RemoteException {
        HubEndpointInfo expectedInfo = endpoint.getAssignedHubEndpointInfo();
        endpoint.unregister();
        ArgumentCaptor<EndpointInfo> statusCaptor = ArgumentCaptor.forClass(EndpointInfo.class);
        verify(mMockEndpointCommunications).unregisterEndpoint(statusCaptor.capture());
        assertThat(statusCaptor.getValue().id.id)
                .isEqualTo(expectedInfo.getIdentifier().getEndpoint());
        assertThat(statusCaptor.getValue().id.hubId)
                .isEqualTo(expectedInfo.getIdentifier().getHub());
        assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(0);
    }
}