Don't register playback/recording change if caller doesn't care it

When calling VirtualDeviceManager#createVirtualAudioDevice(), if the
caller pass null AudioConfigurationChangeCallback parameter, it means
the caller doesn't care the audio playback/recording change in virtual
display, and we should not register it with AudioManager for
optimization.

Bug: 218528439
Test: atest CtsVirtualDevicesTestCases
Change-Id: I00120f2506f593100ef8c523a4f6addffe46597b
This commit is contained in:
yuanjiahsu
2022-03-23 02:11:11 +08:00
committed by Yuanjia Hsu
parent 329edd6026
commit ad1bf17965
12 changed files with 161 additions and 80 deletions

View File

@@ -17,7 +17,8 @@
package android.companion.virtual;
import android.app.PendingIntent;
import android.companion.virtual.audio.IAudioSessionCallback;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.input.VirtualKeyEvent;
@@ -51,7 +52,8 @@ interface IVirtualDevice {
*/
void onAudioSessionStarting(
int displayId,
IAudioSessionCallback callback);
IAudioRoutingCallback routingCallback,
IAudioConfigChangedCallback configChangedCallback);
void onAudioSessionEnded();

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 The Android Open Source Project
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,15 +20,11 @@ import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration;
/**
* Callback to control audio rerouting, notify playback and recording state of applications running
* on virtual device.
* Callback to notify playback and recording state of applications running on virtual device.
*
* @hide
*/
oneway interface IAudioSessionCallback {
/** Updates the set of applications that need to have their audio rerouted. */
void onAppsNeedingAudioRoutingChanged(in int[] appUids);
oneway interface IAudioConfigChangedCallback {
/**
* Called whenever the playback configuration of applications running on virtual device has

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.companion.virtual.audio;
/**
* Callback to control audio rerouting for applications running on virtual device.
*
* @hide
*/
oneway interface IAudioRoutingCallback {
/** Updates the set of applications that need to have their audio rerouted. */
void onAppsNeedingAudioRoutingChanged(in int[] appUids);
}

View File

@@ -76,8 +76,8 @@ public final class VirtualAudioDevice implements Closeable {
* @hide
*/
public VirtualAudioDevice(Context context, IVirtualDevice virtualDevice,
VirtualDisplay virtualDisplay, Executor executor,
AudioConfigurationChangeCallback callback) {
@NonNull VirtualDisplay virtualDisplay, @Nullable Executor executor,
@Nullable AudioConfigurationChangeCallback callback) {
mContext = context;
mVirtualDevice = virtualDevice;
mVirtualDisplay = virtualDisplay;
@@ -105,7 +105,8 @@ public final class VirtualAudioDevice implements Closeable {
try {
mVirtualDevice.onAudioSessionStarting(mVirtualDisplay.getDisplay().getDisplayId(),
/* callback= */ mOngoingSession);
/* routingCallback= */ mOngoingSession,
/* configChangedCallback= */ mOngoingSession.getAudioConfigChangedListener());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -135,7 +136,8 @@ public final class VirtualAudioDevice implements Closeable {
try {
mVirtualDevice.onAudioSessionStarting(mVirtualDisplay.getDisplay().getDisplayId(),
/* callback= */ mOngoingSession);
/* routingCallback= */ mOngoingSession,
/* configChangedCallback= */ mOngoingSession.getAudioConfigChangedListener());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

View File

@@ -50,16 +50,14 @@ import java.util.concurrent.Executor;
* @hide
*/
@VisibleForTesting
public final class VirtualAudioSession extends IAudioSessionCallback.Stub implements
public final class VirtualAudioSession extends IAudioRoutingCallback.Stub implements
UserRestrictionsCallback, Closeable {
private static final String TAG = "VirtualAudioSession";
private final Context mContext;
private final UserRestrictionsDetector mUserRestrictionsDetector;
/** The {@link Executor} for sending {@link AudioConfigurationChangeCallback} to the caller */
private final Executor mExecutor;
@Nullable
private final AudioConfigurationChangeCallback mCallback;
private final AudioConfigChangedCallback mAudioConfigChangedCallback;
private final Object mLock = new Object();
@GuardedBy("mLock")
private final IntArray mReroutedAppUids = new IntArray();
@@ -73,13 +71,44 @@ public final class VirtualAudioSession extends IAudioSessionCallback.Stub implem
@GuardedBy("mLock")
private AudioInjection mAudioInjection;
/**
* Class to receive {@link IAudioConfigChangedCallback} callbacks from service.
*
* @hide
*/
@VisibleForTesting
public static final class AudioConfigChangedCallback extends IAudioConfigChangedCallback.Stub {
private final Executor mExecutor;
private final AudioConfigurationChangeCallback mCallback;
AudioConfigChangedCallback(Context context, Executor executor,
AudioConfigurationChangeCallback callback) {
mExecutor = executor != null ? executor : context.getMainExecutor();
mCallback = callback;
}
@Override
public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
if (mCallback != null) {
mExecutor.execute(() -> mCallback.onPlaybackConfigChanged(configs));
}
}
@Override
public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
if (mCallback != null) {
mExecutor.execute(() -> mCallback.onRecordingConfigChanged(configs));
}
}
}
@VisibleForTesting
public VirtualAudioSession(Context context,
@Nullable AudioConfigurationChangeCallback callback, @Nullable Executor executor) {
mContext = context;
mUserRestrictionsDetector = new UserRestrictionsDetector(context);
mCallback = callback;
mExecutor = executor != null ? executor : context.getMainExecutor();
mAudioConfigChangedCallback = callback == null ? null : new AudioConfigChangedCallback(
context, executor, callback);
}
/**
@@ -129,6 +158,13 @@ public final class VirtualAudioSession extends IAudioSessionCallback.Stub implem
}
}
/** @hide */
@VisibleForTesting
@Nullable
public AudioConfigChangedCallback getAudioConfigChangedListener() {
return mAudioConfigChangedCallback;
}
/** @hide */
@VisibleForTesting
@Nullable
@@ -263,20 +299,6 @@ public final class VirtualAudioSession extends IAudioSessionCallback.Stub implem
}
}
@Override
public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
if (mCallback != null) {
mExecutor.execute(() -> mCallback.onPlaybackConfigChanged(configs));
}
}
@Override
public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
if (mCallback != null) {
mExecutor.execute(() -> mCallback.onRecordingConfigChanged(configs));
}
}
/** @hide */
@VisibleForTesting
public IntArray getReroutedAppUids() {

View File

@@ -167,7 +167,7 @@ public class VirtualAudioSessionTest {
public void onPlaybackConfigChanged_sendsCallback() {
List<AudioPlaybackConfiguration> configs = new ArrayList<>();
mVirtualAudioSession.onPlaybackConfigChanged(configs);
mVirtualAudioSession.getAudioConfigChangedListener().onPlaybackConfigChanged(configs);
verify(mCallback, timeout(2000)).onPlaybackConfigChanged(configs);
}
@@ -176,7 +176,7 @@ public class VirtualAudioSessionTest {
public void onRecordingConfigChanged_sendCallback() {
List<AudioRecordingConfiguration> configs = new ArrayList<>();
mVirtualAudioSession.onRecordingConfigChanged(configs);
mVirtualAudioSession.getAudioConfigChangedListener().onRecordingConfigChanged(configs);
verify(mCallback, timeout(2000)).onRecordingConfigChanged(configs);
}

View File

@@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StringRes;
import android.app.Activity;
@@ -34,7 +35,8 @@ import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.VirtualDeviceManager.ActivityListener;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioSessionCallback;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -269,7 +271,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Override // Binder call
public void onAudioSessionStarting(int displayId, IAudioSessionCallback callback) {
public void onAudioSessionStarting(int displayId,
@NonNull IAudioRoutingCallback routingCallback,
@Nullable IAudioConfigChangedCallback configChangedCallback) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to start audio session");
@@ -283,7 +287,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
if (mVirtualAudioController == null) {
mVirtualAudioController = new VirtualAudioController(mContext);
GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId);
mVirtualAudioController.startListening(gwpc, callback);
mVirtualAudioController.startListening(gwpc, routingCallback,
configChangedCallback);
}
}
}

View File

@@ -51,8 +51,10 @@ final class AudioPlaybackDetector extends AudioManager.AudioPlaybackCallback {
}
void unregister() {
mAudioPlaybackCallback = null;
mAudioManager.unregisterAudioPlaybackCallback(/* cb= */ this);
if (mAudioPlaybackCallback != null) {
mAudioPlaybackCallback = null;
mAudioManager.unregisterAudioPlaybackCallback(/* cb= */ this);
}
}
@Override

View File

@@ -51,8 +51,10 @@ final class AudioRecordingDetector extends AudioManager.AudioRecordingCallback {
}
void unregister() {
mAudioRecordingCallback = null;
mAudioManager.unregisterAudioRecordingCallback(/* cb= */ this);
if (mAudioRecordingCallback != null) {
mAudioRecordingCallback = null;
mAudioManager.unregisterAudioRecordingCallback(/* cb= */ this);
}
}
@Override

View File

@@ -21,7 +21,8 @@ import static android.media.AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.companion.virtual.audio.IAudioSessionCallback;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.content.Context;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
@@ -64,9 +65,10 @@ public final class VirtualAudioController implements AudioPlaybackCallback,
private ArraySet<Integer> mPlayingAppUids = new ArraySet<>();
private GenericWindowPolicyController mGenericWindowPolicyController;
private final Object mCallbackLock = new Object();
@Nullable
@GuardedBy("mCallbackLock")
private IAudioSessionCallback mCallback;
private IAudioRoutingCallback mRoutingCallback;
@GuardedBy("mCallbackLock")
private IAudioConfigChangedCallback mConfigChangedCallback;
public VirtualAudioController(Context context) {
mContext = context;
@@ -80,18 +82,22 @@ public final class VirtualAudioController implements AudioPlaybackCallback,
*/
public void startListening(
@NonNull GenericWindowPolicyController genericWindowPolicyController,
@Nullable IAudioSessionCallback callback) {
@NonNull IAudioRoutingCallback routingCallback,
@Nullable IAudioConfigChangedCallback configChangedCallback) {
mGenericWindowPolicyController = genericWindowPolicyController;
mGenericWindowPolicyController.setRunningAppsChangedListener(/* listener= */ this);
synchronized (mCallbackLock) {
mCallback = callback;
mRoutingCallback = routingCallback;
mConfigChangedCallback = configChangedCallback;
}
synchronized (mLock) {
mRunningAppUids.clear();
mPlayingAppUids.clear();
}
mAudioPlaybackDetector.register(/* callback= */ this);
mAudioRecordingDetector.register(/* callback= */ this);
if (configChangedCallback != null) {
mAudioPlaybackDetector.register(/* callback= */ this);
mAudioRecordingDetector.register(/* callback= */ this);
}
}
/**
@@ -109,7 +115,8 @@ public final class VirtualAudioController implements AudioPlaybackCallback,
mGenericWindowPolicyController = null;
}
synchronized (mCallbackLock) {
mCallback = null;
mRoutingCallback = null;
mConfigChangedCallback = null;
}
}
@@ -169,9 +176,9 @@ public final class VirtualAudioController implements AudioPlaybackCallback,
audioPlaybackConfigurations = findPlaybackConfigurations(configs, mRunningAppUids);
}
synchronized (mCallbackLock) {
if (mCallback != null) {
if (mConfigChangedCallback != null) {
try {
mCallback.onPlaybackConfigChanged(audioPlaybackConfigurations);
mConfigChangedCallback.onPlaybackConfigChanged(audioPlaybackConfigurations);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException when calling onPlaybackConfigChanged", e);
}
@@ -188,9 +195,9 @@ public final class VirtualAudioController implements AudioPlaybackCallback,
audioRecordingConfigurations = findRecordingConfigurations(configs, mRunningAppUids);
}
synchronized (mCallbackLock) {
if (mCallback != null) {
if (mConfigChangedCallback != null) {
try {
mCallback.onRecordingConfigChanged(audioRecordingConfigurations);
mConfigChangedCallback.onRecordingConfigChanged(audioRecordingConfigurations);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException when calling onRecordingConfigChanged", e);
}
@@ -227,9 +234,9 @@ public final class VirtualAudioController implements AudioPlaybackCallback,
}
synchronized (mCallbackLock) {
if (mCallback != null) {
if (mRoutingCallback != null) {
try {
mCallback.onAppsNeedingAudioRoutingChanged(runningUids);
mRoutingCallback.onAppsNeedingAudioRoutingChanged(runningUids);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException when calling updateReroutingApps", e);
}

View File

@@ -40,7 +40,8 @@ import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioSessionCallback;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.content.Context;
import android.content.ContextWrapper;
import android.graphics.Point;
@@ -115,7 +116,9 @@ public class VirtualDeviceManagerServiceTest {
IThermalService mIThermalServiceMock;
private PowerManager mPowerManager;
@Mock
private IAudioSessionCallback mCallback;
private IAudioRoutingCallback mRoutingCallback;
@Mock
private IAudioConfigChangedCallback mConfigChangedCallback;
@Before
public void setUp() {
@@ -258,7 +261,8 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void onAudioSessionStarting_noDisplay_failsSecurityException() {
assertThrows(SecurityException.class,
() -> mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback));
() -> mDeviceImpl.onAudioSessionStarting(
DISPLAY_ID, mRoutingCallback, mConfigChangedCallback));
}
@Test
@@ -300,7 +304,8 @@ public class VirtualDeviceManagerServiceTest {
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
assertThrows(SecurityException.class,
() -> mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback));
() -> mDeviceImpl.onAudioSessionStarting(
DISPLAY_ID, mRoutingCallback, mConfigChangedCallback));
}
@Test
@@ -347,7 +352,7 @@ public class VirtualDeviceManagerServiceTest {
public void onAudioSessionStarting_hasVirtualAudioController() {
mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNotNull();
}
@@ -355,7 +360,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void onAudioSessionEnded_noVirtualAudioController() {
mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
mDeviceImpl.onAudioSessionEnded();
@@ -365,7 +370,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void close_cleanVirtualAudioController() {
mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
mDeviceImpl.close();

View File

@@ -26,7 +26,8 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioSessionCallback;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.content.Context;
import android.content.ContextWrapper;
import android.media.AudioPlaybackConfiguration;
@@ -60,7 +61,10 @@ public class VirtualAudioControllerTest {
private Context mContext;
private VirtualAudioController mVirtualAudioController;
private GenericWindowPolicyController mGenericWindowPolicyController;
@Mock IAudioSessionCallback mCallback;
@Mock
private IAudioRoutingCallback mRoutingCallback;
@Mock
private IAudioConfigChangedCallback mConfigChangedCallback;
@Before
public void setUp() {
@@ -82,42 +86,46 @@ public class VirtualAudioControllerTest {
public void startListening_receivesCallback() throws RemoteException {
ArraySet<Integer> runningUids = new ArraySet<>();
runningUids.add(APP1_UID);
int[] appUids = new int[] {APP1_UID};
int[] appUids = new int[]{APP1_UID};
mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback);
mVirtualAudioController.startListening(
mGenericWindowPolicyController, mRoutingCallback, mConfigChangedCallback);
mGenericWindowPolicyController.onRunningAppsChanged(runningUids);
verify(mCallback).onAppsNeedingAudioRoutingChanged(appUids);
verify(mRoutingCallback).onAppsNeedingAudioRoutingChanged(appUids);
}
@Test
public void stopListening_removesCallback() throws RemoteException {
ArraySet<Integer> runningUids = new ArraySet<>();
runningUids.add(APP1_UID);
int[] appUids = new int[] {APP1_UID};
mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback);
int[] appUids = new int[]{APP1_UID};
mVirtualAudioController.startListening(
mGenericWindowPolicyController, mRoutingCallback, mConfigChangedCallback);
mVirtualAudioController.stopListening();
mGenericWindowPolicyController.onRunningAppsChanged(runningUids);
verify(mCallback, never()).onAppsNeedingAudioRoutingChanged(appUids);
verify(mRoutingCallback, never()).onAppsNeedingAudioRoutingChanged(appUids);
}
@Test
public void onRunningAppsChanged_notifiesAudioRoutingModified() throws RemoteException {
mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback);
mVirtualAudioController.startListening(
mGenericWindowPolicyController, mRoutingCallback, mConfigChangedCallback);
ArraySet<Integer> runningUids = new ArraySet<>();
runningUids.add(APP1_UID);
mVirtualAudioController.onRunningAppsChanged(runningUids);
int[] appUids = new int[] {APP1_UID};
verify(mCallback).onAppsNeedingAudioRoutingChanged(appUids);
int[] appUids = new int[]{APP1_UID};
verify(mRoutingCallback).onAppsNeedingAudioRoutingChanged(appUids);
}
@Test
public void onRunningAppsChanged_audioIsPlaying_doesNothing() throws RemoteException {
mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback);
mVirtualAudioController.startListening(
mGenericWindowPolicyController, mRoutingCallback, mConfigChangedCallback);
mVirtualAudioController.addPlayingAppsForTesting(APP2_UID);
ArraySet<Integer> runningUids = new ArraySet<>();
@@ -125,7 +133,7 @@ public class VirtualAudioControllerTest {
mVirtualAudioController.onRunningAppsChanged(runningUids);
int[] appUids = new int[]{APP1_UID};
verify(mCallback, never()).onAppsNeedingAudioRoutingChanged(appUids);
verify(mRoutingCallback, never()).onAppsNeedingAudioRoutingChanged(appUids);
}
@Test
@@ -145,7 +153,8 @@ public class VirtualAudioControllerTest {
@Test
public void onPlaybackConfigChanged_sendsCallback() throws RemoteException {
mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback);
mVirtualAudioController.startListening(
mGenericWindowPolicyController, mRoutingCallback, mConfigChangedCallback);
ArraySet<Integer> runningUids = new ArraySet<>();
runningUids.add(APP1_UID);
mVirtualAudioController.onRunningAppsChanged(runningUids);
@@ -153,12 +162,13 @@ public class VirtualAudioControllerTest {
mVirtualAudioController.onPlaybackConfigChanged(configs);
verify(mCallback).onPlaybackConfigChanged(configs);
verify(mConfigChangedCallback).onPlaybackConfigChanged(configs);
}
@Test
public void onRecordingConfigChanged_sendsCallback() throws RemoteException {
mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback);
mVirtualAudioController.startListening(
mGenericWindowPolicyController, mRoutingCallback, mConfigChangedCallback);
ArraySet<Integer> runningUids = new ArraySet<>();
runningUids.add(APP1_UID);
mVirtualAudioController.onRunningAppsChanged(runningUids);
@@ -166,7 +176,7 @@ public class VirtualAudioControllerTest {
mVirtualAudioController.onRecordingConfigChanged(configs);
verify(mCallback).onRecordingConfigChanged(configs);
verify(mConfigChangedCallback).onRecordingConfigChanged(configs);
}
private List<AudioPlaybackConfiguration> createPlaybackConfigurations(