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

Commit f4e5814d authored by Biswarup Pal's avatar Biswarup Pal
Browse files

Unregister virtual camera when client binder dies

Test: atest VirtualCameraControllerTest, CtsVirtualCameraTestCases
Bug: 310857519
Change-Id: I03526bbf5f993068a2f0555b2e553d5bbf61906f
parent 5a230697
Loading
Loading
Loading
Loading
+76 −38
Original line number Diff line number Diff line
@@ -27,14 +27,14 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArraySet;
import android.util.ArrayMap;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;
import java.util.Set;
import java.util.Map;

/**
 * Manages the registration and removal of virtual camera from the server side.
@@ -47,10 +47,13 @@ public final class VirtualCameraController implements IBinder.DeathRecipient {
    private static final String VIRTUAL_CAMERA_SERVICE_NAME = "virtual_camera";
    private static final String TAG = "VirtualCameraController";

    private final Object mServiceLock = new Object();

    @GuardedBy("mServiceLock")
    @Nullable private IVirtualCameraService mVirtualCameraService;

    @GuardedBy("mCameras")
    private final Set<VirtualCameraConfig> mCameras = new ArraySet<>();
    private final Map<IBinder, CameraDescriptor> mCameras = new ArrayMap<>();

    public VirtualCameraController() {
        connectVirtualCameraService();
@@ -71,8 +74,12 @@ public final class VirtualCameraController implements IBinder.DeathRecipient {

        try {
            if (registerCameraWithService(cameraConfig)) {
                CameraDescriptor cameraDescriptor =
                        new CameraDescriptor(cameraConfig);
                IBinder binder = cameraConfig.getCallback().asBinder();
                binder.linkToDeath(cameraDescriptor, 0 /* flags */);
                synchronized (mCameras) {
                    mCameras.add(cameraConfig);
                    mCameras.put(binder, cameraDescriptor);
                }
            } else {
                // TODO(b/310857519): Revisit this to find a better way of indicating failure.
@@ -89,26 +96,33 @@ public final class VirtualCameraController implements IBinder.DeathRecipient {
     * @param cameraConfig The {@link VirtualCameraConfig} sent by the client.
     */
    public void unregisterCamera(@NonNull VirtualCameraConfig cameraConfig) {
        try {
            if (mVirtualCameraService == null) {
                Slog.w(TAG, "Virtual camera service is not connected.");
            } else {
                mVirtualCameraService.unregisterCamera(cameraConfig.getCallback().asBinder());
            }
        synchronized (mCameras) {
                mCameras.remove(cameraConfig);
            IBinder binder = cameraConfig.getCallback().asBinder();
            if (!mCameras.containsKey(binder)) {
                Slog.w(TAG, "Virtual camera was not registered.");
            } else {
                connectVirtualCameraServiceIfNeeded();

                try {
                    synchronized (mServiceLock) {
                        mVirtualCameraService.unregisterCamera(binder);
                    }
                    mCameras.remove(binder);
                } catch (RemoteException e) {
                    e.rethrowFromSystemServer();
                }
            }
        }
    }

    /** Return the id of the virtual camera with the given config. */
    public int getCameraId(@NonNull VirtualCameraConfig cameraConfig) {
        connectVirtualCameraServiceIfNeeded();

        try {
            synchronized (mServiceLock) {
                return mVirtualCameraService.getCameraId(cameraConfig.getCallback().asBinder());
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
@@ -117,7 +131,9 @@ public final class VirtualCameraController implements IBinder.DeathRecipient {
    @Override
    public void binderDied() {
        Slog.d(TAG, "Virtual camera service died.");
        synchronized (mServiceLock) {
            mVirtualCameraService = null;
        }
        synchronized (mCameras) {
            mCameras.clear();
        }
@@ -126,12 +142,13 @@ public final class VirtualCameraController implements IBinder.DeathRecipient {
    /** Release resources associated with this controller. */
    public void close() {
        synchronized (mCameras) {
            if (mVirtualCameraService == null) {
                Slog.w(TAG, "Virtual camera service is not connected.");
            } else {
                for (VirtualCameraConfig config : mCameras) {
            if (!mCameras.isEmpty()) {
                connectVirtualCameraServiceIfNeeded();

                synchronized (mServiceLock) {
                    for (IBinder binder : mCameras.keySet()) {
                        try {
                        mVirtualCameraService.unregisterCamera(config.getCallback().asBinder());
                            mVirtualCameraService.unregisterCamera(binder);
                        } catch (RemoteException e) {
                            Slog.w(TAG, "close(): Camera failed to be removed on camera "
                                    + "service.", e);
@@ -140,23 +157,26 @@ public final class VirtualCameraController implements IBinder.DeathRecipient {
                }
                mCameras.clear();
            }
        }
        synchronized (mServiceLock) {
            mVirtualCameraService = null;
        }
    }

    /** Dumps information about this {@link VirtualCameraController} for debugging purposes. */
    public void dump(PrintWriter fout, String indent) {
        fout.println(indent + "VirtualCameraController:");
        indent += indent;
        fout.printf("%sService:%s\n", indent, mVirtualCameraService);
        synchronized (mCameras) {
            fout.printf("%sRegistered cameras:%d%n\n", indent, mCameras.size());
            for (VirtualCameraConfig config : mCameras) {
                fout.printf("%s token: %s\n", indent, config);
            for (CameraDescriptor descriptor : mCameras.values()) {
                fout.printf("%s token: %s\n", indent, descriptor.mConfig);
            }
        }
    }

    private void connectVirtualCameraServiceIfNeeded() {
        synchronized (mServiceLock) {
            // Try to connect to service if not connected already.
            if (mVirtualCameraService == null) {
                connectVirtualCameraService();
@@ -166,6 +186,7 @@ public final class VirtualCameraController implements IBinder.DeathRecipient {
                throw new IllegalStateException("Virtual camera service is not connected.");
            }
        }
    }

    private void connectVirtualCameraService() {
        final long callingId = Binder.clearCallingIdentity();
@@ -188,7 +209,24 @@ public final class VirtualCameraController implements IBinder.DeathRecipient {

    private boolean registerCameraWithService(VirtualCameraConfig config) throws RemoteException {
        VirtualCameraConfiguration serviceConfiguration = getServiceCameraConfiguration(config);
        synchronized (mServiceLock) {
            return mVirtualCameraService.registerCamera(config.getCallback().asBinder(),
                    serviceConfiguration);
        }
    }

    private final class CameraDescriptor implements IBinder.DeathRecipient {

        private final VirtualCameraConfig mConfig;

        CameraDescriptor(VirtualCameraConfig config) {
            mConfig = config;
        }

        @Override
        public void binderDied() {
            Slog.d(TAG, "Virtual camera binder died");
            unregisterCamera(mConfig);
        }
    }
}
+10 −1
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Surface;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -77,6 +78,11 @@ public class VirtualCameraControllerTest {
        when(mVirtualCameraServiceMock.registerCamera(any(), any())).thenReturn(true);
    }

    @After
    public void tearDown() throws Exception {
        mVirtualCameraController.close();
    }

    @Test
    public void registerCamera_registersCamera() throws Exception {
        mVirtualCameraController.registerCamera(createVirtualCameraConfig(
@@ -95,6 +101,8 @@ public class VirtualCameraControllerTest {
    public void unregisterCamera_unregistersCamera() throws Exception {
        VirtualCameraConfig config = createVirtualCameraConfig(
                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1);
        mVirtualCameraController.registerCamera(config);

        mVirtualCameraController.unregisterCamera(config);

        verify(mVirtualCameraServiceMock).unregisterCamera(any());
@@ -107,9 +115,10 @@ public class VirtualCameraControllerTest {
        mVirtualCameraController.registerCamera(createVirtualCameraConfig(
                CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_2));

        mVirtualCameraController.close();

        ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
                ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
        mVirtualCameraController.close();
        verify(mVirtualCameraServiceMock, times(2)).registerCamera(any(),
                configurationCaptor.capture());
        List<VirtualCameraConfiguration> virtualCameraConfigurations =