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

Commit fa6b2d9b authored by Dorin Drimus's avatar Dorin Drimus
Browse files

Enable virtual external cameras (base)

Allow virtual external cameras (with lens facing LENS_FACING_EXTERNAL).
Bug: 375609768
Test: atest VirtualCameraTest
Flag: android.companion.virtualdevice.flags.external_virtual_cameras

Change-Id: I3c3bb176bd514ebc5211ee28527ba894fb18ece9
parent 659fb5a5
Loading
Loading
Loading
Loading
+12 −5
Original line number Diff line number Diff line
@@ -24,8 +24,10 @@ import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.companion.virtual.VirtualDevice;
import android.companion.virtualdevice.flags.Flags;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraMetadata;
import android.os.Parcel;
import android.os.Parcelable;
@@ -281,20 +283,25 @@ public final class VirtualCameraConfig implements Parcelable {
        }

        /**
         * Sets the lens facing direction of the virtual camera, can be either
         * {@link CameraMetadata#LENS_FACING_FRONT} or {@link CameraMetadata#LENS_FACING_BACK}.
         * Sets the lens facing direction of the virtual camera.
         *
         * <p>A {@link VirtualDevice} can have at most one {@link VirtualCamera} with
         * {@link CameraMetadata#LENS_FACING_FRONT} and at most one {@link VirtualCamera} with
         * {@link CameraMetadata#LENS_FACING_BACK}.
         * {@link CameraMetadata#LENS_FACING_BACK}, though it can create multiple cameras with
         * {@link CameraMetadata#LENS_FACING_EXTERNAL}.
         *
         * @param lensFacing The direction that the virtual camera faces relative to the device's
         *                   screen.
         * @see CameraCharacteristics#LENS_FACING
         */
        @NonNull
        public Builder setLensFacing(int lensFacing) {
            if (lensFacing != CameraMetadata.LENS_FACING_BACK
                    && lensFacing != CameraMetadata.LENS_FACING_FRONT) {
            boolean allowLensFacing = lensFacing == CameraMetadata.LENS_FACING_FRONT
                    || lensFacing == CameraMetadata.LENS_FACING_BACK;
            if (Flags.externalVirtualCameras()) {
                allowLensFacing |= lensFacing == CameraMetadata.LENS_FACING_EXTERNAL;
            }
            if (!allowLensFacing) {
                throw new IllegalArgumentException("Unsupported lens facing: " + lensFacing);
            }
            mLensFacing = lensFacing;
+7 −2
Original line number Diff line number Diff line
@@ -26,7 +26,9 @@ import android.companion.virtual.VirtualDeviceParams.DevicePolicy;
import android.companion.virtual.camera.VirtualCameraConfig;
import android.companion.virtualcamera.IVirtualCameraService;
import android.companion.virtualcamera.VirtualCameraConfiguration;
import android.companion.virtualdevice.flags.Flags;
import android.content.AttributionSource;
import android.hardware.camera2.CameraMetadata;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -197,14 +199,17 @@ public final class VirtualCameraController implements IBinder.DeathRecipient {
            throw new IllegalArgumentException(
                    "Cannot create virtual camera with DEVICE_POLICY_DEFAULT for "
                            + "POLICY_TYPE_CAMERA");
        } else if (isLensFacingAlreadyPresent(config.getLensFacing())) {
        } else if (isNonExternalLensFacingAlreadyPresent(config.getLensFacing())) {
            throw new IllegalArgumentException(
                    "Only a single virtual camera can be created with lens facing "
                            + config.getLensFacing());
        }
    }

    private boolean isLensFacingAlreadyPresent(int lensFacing) {
    private boolean isNonExternalLensFacingAlreadyPresent(int lensFacing) {
        if (Flags.externalVirtualCameras() && CameraMetadata.LENS_FACING_EXTERNAL == lensFacing) {
            return false;
        }
        synchronized (mCameras) {
            for (CameraDescriptor cameraDescriptor : mCameras.values()) {
                if (cameraDescriptor.mConfig.getLensFacing() == lensFacing) {
+47 −3
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static android.companion.virtual.camera.VirtualCameraConfig.SENSOR_ORIENT
import static android.graphics.ImageFormat.YUV_420_888;
import static android.graphics.PixelFormat.RGBA_8888;
import static android.hardware.camera2.CameraMetadata.LENS_FACING_BACK;
import static android.hardware.camera2.CameraMetadata.LENS_FACING_EXTERNAL;
import static android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT;

import static com.google.common.truth.Truth.assertThat;
@@ -38,11 +39,15 @@ import android.companion.virtual.camera.VirtualCameraCallback;
import android.companion.virtual.camera.VirtualCameraConfig;
import android.companion.virtualcamera.IVirtualCameraService;
import android.companion.virtualcamera.VirtualCameraConfiguration;
import android.companion.virtualdevice.flags.Flags;
import android.content.AttributionSource;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.TestableLooper;

import junitparams.JUnitParamsRunner;
@@ -50,12 +55,14 @@ import junitparams.Parameters;

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

import java.util.ArrayList;
import java.util.List;

@Presubmit
@@ -89,6 +96,8 @@ public class VirtualCameraControllerTest {
    private final HandlerExecutor mCallbackHandler =
            new HandlerExecutor(new Handler(Looper.getMainLooper()));

    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
@@ -168,7 +177,7 @@ public class VirtualCameraControllerTest {
                CAMERA_LENS_FACING_2);
    }

    @Parameters(method = "getAllLensFacingDirections")
    @Parameters(method = "getSingleCamerasLensFacingDirections")
    @Test
    public void registerMultipleSameLensFacingCameras_withCustomCameraPolicy_throwsException(
            int lensFacing) {
@@ -182,6 +191,30 @@ public class VirtualCameraControllerTest {
                        AttributionSource.myAttributionSource()));
    }

    @Test
    @EnableFlags(Flags.FLAG_EXTERNAL_VIRTUAL_CAMERAS)
    public void registerMultipleExternalCameras_withCustomCameraPolicy_succeeds() {
        mVirtualCameraController.registerCamera(
                createVirtualCameraConfig(CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1,
                        CAMERA_MAX_FPS_1, CAMERA_NAME_1, CAMERA_SENSOR_ORIENTATION_1,
                        LENS_FACING_EXTERNAL), AttributionSource.myAttributionSource());

        mVirtualCameraController.registerCamera(
                createVirtualCameraConfig(CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2,
                        CAMERA_MAX_FPS_2, CAMERA_NAME_2, CAMERA_SENSOR_ORIENTATION_2,
                        LENS_FACING_EXTERNAL), AttributionSource.myAttributionSource());
    }

    @Test
    @DisableFlags(Flags.FLAG_EXTERNAL_VIRTUAL_CAMERAS)
    public void registerExternalCameras_withCustomCameraPolicy_throwsException_whenNotSupported() {
        assertThrows(IllegalArgumentException.class,
                () -> mVirtualCameraController.registerCamera(
                createVirtualCameraConfig(CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1,
                        CAMERA_MAX_FPS_1, CAMERA_NAME_1, CAMERA_SENSOR_ORIENTATION_1,
                        LENS_FACING_EXTERNAL), AttributionSource.myAttributionSource()));
    }

    @Parameters(method = "getAllLensFacingDirections")
    @Test
    public void registerCamera_withDefaultCameraPolicy_throwsException(int lensFacing) {
@@ -218,10 +251,21 @@ public class VirtualCameraControllerTest {
        assertThat(configuration.lensFacing).isEqualTo(lensFacing);
    }

    private static Integer[] getAllLensFacingDirections() {
    @SuppressWarnings("unused") // Parameter for parametrized tests
    private static Integer[] getSingleCamerasLensFacingDirections() {
        return new Integer[]{
                LENS_FACING_BACK,
                LENS_FACING_FRONT
                LENS_FACING_FRONT,
        };
    }

    @SuppressWarnings("unused") // Parameter for parametrized tests
    private static List<Integer> getAllLensFacingDirections() {
        List<Integer> lensFacingDirections = new ArrayList<>(
                List.of(LENS_FACING_BACK, LENS_FACING_FRONT));
        if (Flags.externalVirtualCameras()) {
            lensFacingDirections.add(LENS_FACING_EXTERNAL);
        }
        return lensFacingDirections;
    }
}