From 5536865e01b65437c8e0ddded1f3971db20cd509 Mon Sep 17 00:00:00 2001 From: yuanjiahsu Date: Fri, 11 Feb 2022 11:46:24 +0800 Subject: [PATCH] Clean up audio resources when VirtualDevice is closed Limits only one VirtualAudioDevice can be created per VirtualDevice. When VirtualDevice is closed, the associated VirtualAudioDevice and VirtualAudioController will be closed as well. Bug: 218751752 Test: atest FrameworksServicesTests:com.android.server.companion.virtual Change-Id: I6aa8b8af8b526fb52bbb4b23305f28182012803d --- .../virtual/VirtualDeviceManager.java | 22 ++++++++++++++----- .../audio/UserRestrictionsDetector.java | 7 +++--- .../virtual/audio/VirtualAudioDevice.java | 7 +++++- .../virtual/audio/VirtualAudioSession.java | 4 +++- .../companion/virtual/VirtualDeviceImpl.java | 4 ++++ .../VirtualDeviceManagerServiceTest.java | 10 +++++++++ 6 files changed, 44 insertions(+), 10 deletions(-) diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index d50a6ba3b56dc..99ce14743a6f8 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -148,6 +148,8 @@ public final class VirtualDeviceManager { } } }; + @Nullable + private VirtualAudioDevice mVirtualAudioDevice; private VirtualDevice( IVirtualDeviceManager service, @@ -255,8 +257,8 @@ public final class VirtualDeviceManager { } /** - * Closes the virtual device, stopping and tearing down any virtual displays, - * audio policies, and event injection that's currently in progress. + * Closes the virtual device, stopping and tearing down any virtual displays, associated + * virtual audio device, and event injection that's currently in progress. */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close() { @@ -265,6 +267,10 @@ public final class VirtualDeviceManager { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + if (mVirtualAudioDevice != null) { + mVirtualAudioDevice.close(); + mVirtualAudioDevice = null; + } } /** @@ -351,8 +357,10 @@ public final class VirtualDeviceManager { * Creates a VirtualAudioDevice, capable of recording audio emanating from this device, * or injecting audio from another device. * - *

Note: This object does not support capturing privileged playback, such as voice call - * audio. + *

Note: One {@link VirtualDevice} can only create one {@link VirtualAudioDevice}, so + * calling this method multiple times will return the same instance. When + * {@link VirtualDevice#close()} is called, the associated {@link VirtualAudioDevice} will + * also be closed automatically. * * @param display The target virtual display to capture from and inject into. * @param executor The {@link Executor} object for the thread on which to execute @@ -368,7 +376,11 @@ public final class VirtualDeviceManager { @NonNull VirtualDisplay display, @Nullable Executor executor, @Nullable AudioConfigurationChangeCallback callback) { - return new VirtualAudioDevice(mContext, mVirtualDevice, display, executor, callback); + if (mVirtualAudioDevice == null) { + mVirtualAudioDevice = new VirtualAudioDevice( + mContext, mVirtualDevice, display, executor, callback); + } + return mVirtualAudioDevice; } /** diff --git a/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java b/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java index 5c246d3657518..c816da72b0fff 100644 --- a/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java +++ b/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java @@ -60,7 +60,6 @@ final class UserRestrictionsDetector extends BroadcastReceiver { /** Registers user restrictions change. */ void register(@NonNull UserRestrictionsCallback callback) { mUserRestrictionsCallback = callback; - IntentFilter filter = new IntentFilter(); filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED); mContext.registerReceiver(/* receiver= */ this, filter); @@ -73,8 +72,10 @@ final class UserRestrictionsDetector extends BroadcastReceiver { /** Unregisters user restrictions change. */ void unregister() { - mUserRestrictionsCallback = null; - mContext.unregisterReceiver(/* receiver= */ this); + if (mUserRestrictionsCallback != null) { + mUserRestrictionsCallback = null; + mContext.unregisterReceiver(/* receiver= */ this); + } } @Override diff --git a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java index 38e37ec0c316c..3f7299fbb09e2 100644 --- a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java +++ b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java @@ -24,6 +24,7 @@ import android.companion.virtual.IVirtualDevice; import android.content.Context; import android.hardware.display.VirtualDisplay; import android.media.AudioFormat; +import android.media.AudioManager; import android.media.AudioPlaybackConfiguration; import android.media.AudioRecordingConfiguration; import android.os.RemoteException; @@ -96,7 +97,7 @@ public final class VirtualAudioDevice implements Closeable { if (mOngoingSession != null && mOngoingSession.getAudioInjection() != null) { throw new IllegalStateException("Cannot start an audio injection while a session is " - + "ongoing. Call close() on this device first to end the previous injection."); + + "ongoing. Call close() on this device first to end the previous session."); } if (mOngoingSession == null) { mOngoingSession = new VirtualAudioSession(mContext, mCallback, mExecutor); @@ -114,6 +115,9 @@ public final class VirtualAudioDevice implements Closeable { /** * Begins recording audio emanating from this device. * + *

Note: This method does not support capturing privileged playback, which means the + * application can opt out of capturing by {@link AudioManager#setAllowedCapturePolicy(int)}. + * * @return An {@link AudioCapture} containing the recorded audio. */ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) @@ -150,6 +154,7 @@ public final class VirtualAudioDevice implements Closeable { return mOngoingSession != null ? mOngoingSession.getAudioInjection() : null; } + /** Stops audio capture and injection then releases all the resources */ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) @Override public void close() { diff --git a/core/java/android/companion/virtual/audio/VirtualAudioSession.java b/core/java/android/companion/virtual/audio/VirtualAudioSession.java index bc71bd65e9440..c6a10456c3319 100644 --- a/core/java/android/companion/virtual/audio/VirtualAudioSession.java +++ b/core/java/android/companion/virtual/audio/VirtualAudioSession.java @@ -120,7 +120,7 @@ public final class VirtualAudioSession extends IAudioSessionCallback.Stub implem @NonNull public AudioInjection startAudioInjection(@NonNull AudioFormat injectionFormat) { Objects.requireNonNull(injectionFormat, "injectionFormat must not be null"); - mUserRestrictionsDetector.register(/* callback= */ this); + synchronized (mLock) { if (mAudioInjection != null) { throw new IllegalStateException( @@ -130,6 +130,8 @@ public final class VirtualAudioSession extends IAudioSessionCallback.Stub implem mInjectionFormat = injectionFormat; mAudioInjection = new AudioInjection(); mAudioInjection.play(); + + mUserRestrictionsDetector.register(/* callback= */ this); mAudioInjection.setSilent(mUserRestrictionsDetector.isUnmuteMicrophoneDisallowed()); return mAudioInjection; } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 387d911672a8f..c0a904fe3d9a6 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -235,6 +235,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub }); mPerDisplayWakelocks.clear(); } + if (mVirtualAudioController != null) { + mVirtualAudioController.stopListening(); + mVirtualAudioController = null; + } } mListener.onClose(mAssociationInfo.getId()); mAppToken.unlinkToDeath(this, 0); diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 2ae285409b731..9aac81c34edb9 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -360,6 +360,16 @@ public class VirtualDeviceManagerServiceTest { assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNull(); } + @Test + public void close_cleanVirtualAudioController() { + mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID); + mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback); + + mDeviceImpl.close(); + + assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNull(); + } + @Test public void sendKeyEvent_noFd() { assertThrows(