Loading services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java +76 −38 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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(); Loading @@ -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. Loading @@ -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(); } Loading @@ -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(); } Loading @@ -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); Loading @@ -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(); Loading @@ -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(); Loading @@ -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); } } } services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java +10 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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( Loading @@ -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()); Loading @@ -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 = Loading Loading
services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java +76 −38 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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(); Loading @@ -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. Loading @@ -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(); } Loading @@ -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(); } Loading @@ -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); Loading @@ -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(); Loading @@ -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(); Loading @@ -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); } } }
services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java +10 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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( Loading @@ -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()); Loading @@ -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 = Loading