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

Commit 68f917e3 authored by Biswarup Pal's avatar Biswarup Pal Committed by Android (Google) Code Review
Browse files

Merge "Unregister virtual camera when client binder dies" into main

parents e21b7f41 f4e5814d
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 =