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

Commit 47520212 authored by Jan Sebechlebsky's avatar Jan Sebechlebsky Committed by Ján Sebechlebský
Browse files

Move all sensor related bussiness logic to SensorController.

Also, sensors can be only registered once during VD creation
and destroyed only together with VD. Make this more explicit
in the code. Note that this doesn't change the the existing API
behavior.

Bug: 278900649
Test: atest SensorControllerTest
Test: atest CtsVirtualDevicesTestCases

Change-Id: Ib46780f6471aeb90ee5900ad0635b0b9df602d1e
parent 78f60a28
Loading
Loading
Loading
Loading
+60 −23
Original line number Diff line number Diff line
@@ -18,17 +18,20 @@ package com.android.server.companion.virtual;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.sensor.IVirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.hardware.SensorDirectChannel;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SharedMemory;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -36,6 +39,9 @@ import com.android.server.LocalServices;
import com.android.server.sensors.SensorManagerInternal;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
@@ -54,46 +60,64 @@ public class SensorController {

    private final Object mLock = new Object();
    private final int mVirtualDeviceId;

    @GuardedBy("mLock")
    private final ArrayMap<IBinder, SensorDescriptor> mSensorDescriptors = new ArrayMap<>();

    // This device's sensors, keyed by sensor handle.
    @GuardedBy("mLock")
    private SparseArray<VirtualSensor> mVirtualSensors = new SparseArray<>();
    @GuardedBy("mLock")
    private List<VirtualSensor> mVirtualSensorList = null;

    @NonNull
    private final SensorManagerInternal.RuntimeSensorCallback mRuntimeSensorCallback;
    private final SensorManagerInternal mSensorManagerInternal;
    private final VirtualDeviceManagerInternal mVdmInternal;

    public SensorController(int virtualDeviceId,
            @Nullable IVirtualSensorCallback virtualSensorCallback) {
    public SensorController(@NonNull IVirtualDevice virtualDevice, int virtualDeviceId,
            @Nullable IVirtualSensorCallback virtualSensorCallback,
            @NonNull List<VirtualSensorConfig> sensors) {
        mVirtualDeviceId = virtualDeviceId;
        mRuntimeSensorCallback = new RuntimeSensorCallbackWrapper(virtualSensorCallback);
        mSensorManagerInternal = LocalServices.getService(SensorManagerInternal.class);
        mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
        createSensors(virtualDevice, sensors);
    }

    void close() {
        synchronized (mLock) {
            mSensorDescriptors.values().forEach(
                    descriptor -> mSensorManagerInternal.removeRuntimeSensor(
                            descriptor.getHandle()));
                    descriptor -> mSensorManagerInternal.removeRuntimeSensor(descriptor.mHandle));
            mSensorDescriptors.clear();
            mVirtualSensors.clear();
            mVirtualSensorList = null;
        }
    }

    int createSensor(@NonNull IBinder sensorToken, @NonNull VirtualSensorConfig config) {
        Objects.requireNonNull(sensorToken);
        Objects.requireNonNull(config);
    private void createSensors(@NonNull IVirtualDevice virtualDevice,
            @NonNull List<VirtualSensorConfig> configs) {
        Objects.requireNonNull(virtualDevice);
        final long token = Binder.clearCallingIdentity();
        try {
            return createSensorInternal(sensorToken, config);
            for (VirtualSensorConfig config : configs) {
                createSensorInternal(virtualDevice, config);
            }
        } catch (SensorCreationException e) {
            throw new RuntimeException(
                    "Failed to create virtual sensor '" + config.getName() + "'.", e);
            throw new RuntimeException("Failed to create virtual sensor", e);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    private int createSensorInternal(IBinder sensorToken, VirtualSensorConfig config)
    private void createSensorInternal(@NonNull IVirtualDevice virtualDevice,
            @NonNull VirtualSensorConfig config)
            throws SensorCreationException {
        Objects.requireNonNull(config);
        if (config.getType() <= 0) {
            throw new SensorCreationException("Received an invalid virtual sensor type.");
            throw new SensorCreationException(
                    "Received an invalid virtual sensor type (config name '" + config.getName()
                            + "').");
        }
        final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId,
                config.getType(), config.getName(),
@@ -101,15 +125,20 @@ public class SensorController {
                config.getResolution(), config.getPower(), config.getMinDelay(),
                config.getMaxDelay(), config.getFlags(), mRuntimeSensorCallback);
        if (handle <= 0) {
            throw new SensorCreationException("Received an invalid virtual sensor handle.");
            throw new SensorCreationException(
                    "Received an invalid virtual sensor handle '" + config.getName() + "'.");
        }

        synchronized (mLock) {
        SensorDescriptor sensorDescriptor = new SensorDescriptor(
                handle, config.getType(), config.getName());
        final IBinder sensorToken =
                new Binder("android.hardware.sensor.VirtualSensor:" + config.getName());
        VirtualSensor sensor = new VirtualSensor(handle, config.getType(), config.getName(),
                virtualDevice, sensorToken);
        synchronized (mLock) {
            mSensorDescriptors.put(sensorToken, sensorDescriptor);
            mVirtualSensors.put(handle, sensor);
        }
        return handle;
    }

    boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
@@ -126,17 +155,25 @@ public class SensorController {
        }
    }

    void unregisterSensor(@NonNull IBinder token) {
        Objects.requireNonNull(token);
    @Nullable
    VirtualSensor getSensorByHandle(int handle) {
        synchronized (mLock) {
            final SensorDescriptor sensorDescriptor = mSensorDescriptors.remove(token);
            if (sensorDescriptor == null) {
                throw new IllegalArgumentException("Could not unregister sensor for given token");
            }
            mSensorManagerInternal.removeRuntimeSensor(sensorDescriptor.getHandle());
            return mVirtualSensors.get(handle);
        }
    }

    List<VirtualSensor> getSensorList() {
        synchronized (mLock) {
            if (mVirtualSensorList == null) {
                mVirtualSensorList = new ArrayList<>(mVirtualSensors.size());
                for (int i = 0; i < mVirtualSensors.size(); ++i) {
                    mVirtualSensorList.add(mVirtualSensors.valueAt(i));
                }
                mVirtualSensorList = Collections.unmodifiableList(mVirtualSensorList);
            }
            return mVirtualSensorList;
        }
    }

    void dump(@NonNull PrintWriter fout) {
        fout.println("    SensorController: ");
+10 −49
Original line number Diff line number Diff line
@@ -42,7 +42,6 @@ import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.content.ComponentName;
import android.content.Context;
@@ -94,7 +93,6 @@ import com.android.server.companion.virtual.audio.VirtualAudioController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -157,11 +155,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
    @GuardedBy("mVirtualDeviceLock")
    @Nullable
    private LocaleList mLocaleList = null;
    // This device's sensors, keyed by sensor handle.
    @GuardedBy("mVirtualDeviceLock")
    private SparseArray<VirtualSensor> mVirtualSensors = new SparseArray<>();
    @GuardedBy("mVirtualDeviceLock")
    private List<VirtualSensor> mVirtualSensorList = null;

    private ActivityListener createListenerAdapter() {
        return new ActivityListener() {
@@ -218,7 +211,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
                ownerUid,
                deviceId,
                /* inputController= */ null,
                /* sensorController= */ null,
                cameraAccessController,
                pendingTrampolineCallback,
                activityListener,
@@ -237,7 +229,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
            int ownerUid,
            int deviceId,
            InputController inputController,
            SensorController sensorController,
            CameraAccessController cameraAccessController,
            PendingTrampolineCallback pendingTrampolineCallback,
            IVirtualDeviceActivityListener activityListener,
@@ -266,15 +257,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
        } else {
            mInputController = inputController;
        }
        if (sensorController == null) {
            mSensorController = new SensorController(mDeviceId, mParams.getVirtualSensorCallback());
        } else {
            mSensorController = sensorController;
        }
        final List<VirtualSensorConfig> virtualSensorConfigs = mParams.getVirtualSensorConfigs();
        for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
            createVirtualSensor(virtualSensorConfigs.get(i));
        }
        mSensorController = new SensorController(this, mDeviceId,
                mParams.getVirtualSensorCallback(), mParams.getVirtualSensorConfigs());
        mCameraAccessController = cameraAccessController;
        mCameraAccessController.startObservingIfNeeded();
        try {
@@ -284,6 +268,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
        }
    }

    @VisibleForTesting
    SensorController getSensorControllerForTest() {
        return mSensorController;
    }

    /**
     * Returns the flags that should be added to any virtual displays created on this virtual
     * device.
@@ -426,8 +415,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
                    virtualDisplaysToBeReleased[i] = mVirtualDisplays.valueAt(i);
                }
                mVirtualDisplays.clear();
                mVirtualSensorList = null;
                mVirtualSensors.clear();
            }
            // Destroy the display outside locked section.
            for (VirtualDisplayWrapper virtualDisplayWrapper : virtualDisplaysToBeReleased) {
@@ -723,43 +710,17 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
        }
    }

    private void createVirtualSensor(@NonNull VirtualSensorConfig config) {
        final IBinder sensorToken =
                new Binder("android.hardware.sensor.VirtualSensor:" + config.getName());
        final long ident = Binder.clearCallingIdentity();
        try {
            int handle = mSensorController.createSensor(sensorToken, config);
            VirtualSensor sensor = new VirtualSensor(handle, config.getType(), config.getName(),
                    this, sensorToken);
            synchronized (mVirtualDeviceLock) {
                mVirtualSensors.put(handle, sensor);
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    @Override // Binder call
    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
    @Nullable
    public List<VirtualSensor> getVirtualSensorList() {
        super.getVirtualSensorList_enforcePermission();
        synchronized (mVirtualDeviceLock) {
            if (mVirtualSensorList == null) {
                mVirtualSensorList = new ArrayList<>();
                for (int i = 0; i < mVirtualSensors.size(); ++i) {
                    mVirtualSensorList.add(mVirtualSensors.valueAt(i));
                }
                mVirtualSensorList = Collections.unmodifiableList(mVirtualSensorList);
            }
            return mVirtualSensorList;
        }
        return mSensorController.getSensorList();
    }

    @Nullable
    VirtualSensor getVirtualSensorByHandle(int handle) {
        synchronized (mVirtualDeviceLock) {
            return mVirtualSensors.get(handle);
        }
        return mSensorController.getSensorByHandle(handle);
    }

    @Override // Binder call
+62 −31
Original line number Diff line number Diff line
@@ -23,15 +23,19 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;

import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.sensor.IVirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.hardware.Sensor;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -39,12 +43,16 @@ import android.testing.TestableLooper;
import com.android.server.LocalServices;
import com.android.server.sensors.SensorManagerInternal;

import com.google.common.collect.Iterables;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.List;

@Presubmit
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -54,14 +62,17 @@ public class SensorControllerTest {
    private static final String VIRTUAL_SENSOR_NAME = "VirtualAccelerometer";
    private static final int SENSOR_HANDLE = 7;

    private static final int VIRTUAL_SENSOR_TYPE = Sensor.TYPE_ACCELEROMETER;

    @Mock
    private SensorManagerInternal mSensorManagerInternalMock;
    @Mock
    private IVirtualSensorCallback mVirtualSensorCallback;
    private SensorController mSensorController;
    @Mock
    private IVirtualDevice mVirtualDevice;

    private VirtualSensorEvent mSensorEvent;
    private VirtualSensorConfig mVirtualSensorConfig;
    private IBinder mSensorToken;

    @Before
    public void setUp() throws Exception {
@@ -70,12 +81,10 @@ public class SensorControllerTest {
        LocalServices.removeServiceForTest(SensorManagerInternal.class);
        LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);

        mSensorController = new SensorController(VIRTUAL_DEVICE_ID, mVirtualSensorCallback);
        mSensorEvent = new VirtualSensorEvent.Builder(new float[] { 1f, 2f, 3f}).build();
        mVirtualSensorConfig =
                new VirtualSensorConfig.Builder(Sensor.TYPE_ACCELEROMETER, VIRTUAL_SENSOR_NAME)
                new VirtualSensorConfig.Builder(VIRTUAL_SENSOR_TYPE, VIRTUAL_SENSOR_NAME)
                        .build();
        mSensorToken = new Binder("sensorToken");
    }

    @Test
@@ -86,62 +95,84 @@ public class SensorControllerTest {

        Throwable thrown = assertThrows(
                RuntimeException.class,
                () -> mSensorController.createSensor(mSensorToken, mVirtualSensorConfig));
                () -> new SensorController(mVirtualDevice, VIRTUAL_DEVICE_ID,
                        mVirtualSensorCallback, List.of(mVirtualSensorConfig)));

        assertThat(thrown.getCause().getMessage())
                .contains("Received an invalid virtual sensor handle");
    }

    @Test
    public void createSensor_success() {
        doCreateSensorSuccessfully();
    public void createSensor_success() throws Exception {
        SensorController sensorController = doCreateSensorSuccessfully();

        assertThat(mSensorController.getSensorDescriptors()).isNotEmpty();
        assertThat(sensorController.getSensorDescriptors()).isNotEmpty();
    }

    @Test
    public void sendSensorEvent_invalidToken_throwsException() {
        doCreateSensorSuccessfully();
    public void getSensorByHandle_success() throws Exception {
        SensorController sensorController = doCreateSensorSuccessfully();

        assertThrows(
                IllegalArgumentException.class,
                () -> mSensorController.sendSensorEvent(
                        new Binder("invalidSensorToken"), mSensorEvent));
        VirtualSensor sensor = sensorController.getSensorByHandle(SENSOR_HANDLE);

        assertThat(sensor).isNotNull();
        assertThat(sensor.getHandle()).isEqualTo(SENSOR_HANDLE);
        assertThat(sensor.getDeviceId()).isEqualTo(VIRTUAL_DEVICE_ID);
        assertThat(sensor.getType()).isEqualTo(VIRTUAL_SENSOR_TYPE);
    }

    @Test
    public void sendSensorEvent_success() {
        doCreateSensorSuccessfully();
    public void getSensorByHandle_invalidHandle_returnsNull() throws Exception {
        SensorController sensorController = doCreateSensorSuccessfully();
        final int invalidSensorHandle = 123456;

        mSensorController.sendSensorEvent(mSensorToken, mSensorEvent);
        verify(mSensorManagerInternalMock).sendSensorEvent(
                SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, mSensorEvent.getTimestampNanos(),
                mSensorEvent.getValues());
        assertThat(sensorController.getSensorByHandle(invalidSensorHandle)).isNull();
    }

    @Test
    public void unregisterSensor_invalidToken_throwsException() {
        doCreateSensorSuccessfully();
    public void sendSensorEvent_invalidToken_throwsException() throws Exception {
        SensorController sensorController = doCreateSensorSuccessfully();

        assertThrows(
                IllegalArgumentException.class,
                () -> mSensorController.unregisterSensor(new Binder("invalidSensorToken")));
                () -> sensorController.sendSensorEvent(
                        new Binder("invalidSensorToken"), mSensorEvent));
    }

    @Test
    public void unregisterSensor_success() {
        doCreateSensorSuccessfully();
    public void sendSensorEvent_success() throws Exception {
        SensorController sensorController = doCreateSensorSuccessfully();

        clearInvocations(mSensorManagerInternalMock);
        IBinder token = Iterables.getOnlyElement(sensorController.getSensorDescriptors().keySet());

        mSensorController.unregisterSensor(mSensorToken);
        sensorController.sendSensorEvent(token, mSensorEvent);
        verify(mSensorManagerInternalMock).sendSensorEvent(
                SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, mSensorEvent.getTimestampNanos(),
                mSensorEvent.getValues());
    }

    @Test
    public void close_unregistersSensors() throws Exception {
        SensorController sensorController = doCreateSensorSuccessfully();

        sensorController.close();
        verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
        assertThat(mSensorController.getSensorDescriptors()).isEmpty();
        assertThat(sensorController.getSensorDescriptors()).isEmpty();
    }

    private void doCreateSensorSuccessfully() {
    private SensorController doCreateSensorSuccessfully() throws RemoteException {
        doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
                anyInt(), anyInt(), anyString(), anyString(), anyFloat(), anyFloat(), anyFloat(),
                anyInt(), anyInt(), anyInt(), any());
        assertThat(mSensorController.createSensor(mSensorToken, mVirtualSensorConfig))
                .isEqualTo(SENSOR_HANDLE);
        doReturn(VIRTUAL_DEVICE_ID).when(mVirtualDevice).getDeviceId();

        SensorController sensorController = new SensorController(mVirtualDevice, VIRTUAL_DEVICE_ID,
                mVirtualSensorCallback, List.of(mVirtualSensorConfig));

        List<VirtualSensor> sensors = sensorController.getSensorList();
        assertThat(sensors).hasSize(1);
        assertThat(sensors.get(0).getHandle()).isEqualTo(SENSOR_HANDLE);
        return sensorController;
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -359,7 +359,6 @@ public class VirtualDeviceManagerServiceTest {
        mInputController = new InputController(mNativeWrapperMock,
                new Handler(TestableLooper.get(this).getLooper()),
                mContext.getSystemService(WindowManager.class), threadVerifier);
        mSensorController = new SensorController(VIRTUAL_DEVICE_ID_1, mVirtualSensorCallback);
        mCameraAccessController =
                new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback);

@@ -370,6 +369,7 @@ public class VirtualDeviceManagerServiceTest {
        mLocalService = mVdms.getLocalServiceInstance();
        mVdm = mVdms.new VirtualDeviceManagerImpl();
        mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1);
        mSensorController = mDeviceImpl.getSensorControllerForTest();
    }

    @After
@@ -1722,7 +1722,7 @@ public class VirtualDeviceManagerServiceTest {
            VirtualDeviceParams params) {
        VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
                mAssociationInfo, mVdms, new Binder(), ownerUid, virtualDeviceId,
                mInputController, mSensorController, mCameraAccessController
                mInputController, mCameraAccessController
                /* onDeviceCloseListener= */ /*deviceId -> mVdms.removeVirtualDevice(deviceId)*/,
                mPendingTrampolineCallback, mActivityListener, mSoundEffectListener,
                mRunningAppsChangedCallback, params, new DisplayManagerGlobal(mIDisplayManager));