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
This commit is contained in:
yuanjiahsu
2022-02-11 11:46:24 +08:00
committed by Yuanjia Hsu
parent 171380fd37
commit 5536865e01
6 changed files with 44 additions and 10 deletions

View File

@@ -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.
*
* <p>Note: This object does not support capturing privileged playback, such as voice call
* audio.
* <p>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;
}
/**

View File

@@ -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

View File

@@ -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.
*
* <p>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() {

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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(