Merge "SpatializerHelper: fix canBeSpatializedOnDevice NPE" into tm-qpr-dev am: e455bc8e0a

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/22201117

Change-Id: I31d15a7b043c4a45c86d270048ca537ebbd2dfa1
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
TreeHugger Robot
2023-03-24 16:59:30 +00:00
committed by Automerger Merge Worker
2 changed files with 132 additions and 40 deletions

View File

@@ -106,12 +106,12 @@ public class SpatializerHelper {
}; };
// Spatializer state machine // Spatializer state machine
private static final int STATE_UNINITIALIZED = 0; /*package*/ static final int STATE_UNINITIALIZED = 0;
private static final int STATE_NOT_SUPPORTED = 1; /*package*/ static final int STATE_NOT_SUPPORTED = 1;
private static final int STATE_DISABLED_UNAVAILABLE = 3; /*package*/ static final int STATE_DISABLED_UNAVAILABLE = 3;
private static final int STATE_ENABLED_UNAVAILABLE = 4; /*package*/ static final int STATE_ENABLED_UNAVAILABLE = 4;
private static final int STATE_ENABLED_AVAILABLE = 5; /*package*/ static final int STATE_ENABLED_AVAILABLE = 5;
private static final int STATE_DISABLED_AVAILABLE = 6; /*package*/ static final int STATE_DISABLED_AVAILABLE = 6;
private int mState = STATE_UNINITIALIZED; private int mState = STATE_UNINITIALIZED;
private boolean mFeatureEnabled = false; private boolean mFeatureEnabled = false;
@@ -147,9 +147,9 @@ public class SpatializerHelper {
.setSampleRate(48000) .setSampleRate(48000)
.setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
.build(); .build();
// device array to store the routing for the default attributes and format, size 1 because // device array to store the routing for the default attributes and format, initialized to
// media is never expected to be duplicated // an empty list as routing hasn't been established yet
private static final AudioDeviceAttributes[] ROUTING_DEVICES = new AudioDeviceAttributes[1]; private static ArrayList<AudioDeviceAttributes> sRoutingDevices = new ArrayList<>(0);
//--------------------------------------------------------------- //---------------------------------------------------------------
// audio device compatibility / enabled // audio device compatibility / enabled
@@ -184,11 +184,6 @@ public class SpatializerHelper {
SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault; SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault;
} }
synchronized void initForTest(boolean hasBinaural, boolean hasTransaural) {
mBinauralSupported = hasBinaural;
mTransauralSupported = hasTransaural;
}
synchronized void init(boolean effectExpected, @Nullable String settings) { synchronized void init(boolean effectExpected, @Nullable String settings) {
loglogi("init effectExpected=" + effectExpected); loglogi("init effectExpected=" + effectExpected);
if (!effectExpected) { if (!effectExpected) {
@@ -322,8 +317,7 @@ public class SpatializerHelper {
return; return;
} }
mState = STATE_DISABLED_UNAVAILABLE; mState = STATE_DISABLED_UNAVAILABLE;
mASA.getDevicesForAttributes( sRoutingDevices = getRoutingDevices(DEFAULT_ATTRIBUTES);
DEFAULT_ATTRIBUTES, false /* forVolume */).toArray(ROUTING_DEVICES);
// note at this point mSpat is still not instantiated // note at this point mSpat is still not instantiated
} }
@@ -365,34 +359,35 @@ public class SpatializerHelper {
case STATE_DISABLED_AVAILABLE: case STATE_DISABLED_AVAILABLE:
break; break;
} }
mASA.getDevicesForAttributes(
DEFAULT_ATTRIBUTES, false /* forVolume */).toArray(ROUTING_DEVICES); sRoutingDevices = getRoutingDevices(DEFAULT_ATTRIBUTES);
// check validity of routing information // check validity of routing information
if (ROUTING_DEVICES[0] == null) { if (sRoutingDevices.isEmpty()) {
logloge("onRoutingUpdated: device is null, no Spatial Audio"); logloge("onRoutingUpdated: no device, no Spatial Audio");
setDispatchAvailableState(false); setDispatchAvailableState(false);
// not changing the spatializer level as this is likely a transient state // not changing the spatializer level as this is likely a transient state
return; return;
} }
final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
// is media routed to a new device? // is media routed to a new device?
if (isWireless(ROUTING_DEVICES[0].getType())) { if (isWireless(currentDevice.getType())) {
addWirelessDeviceIfNew(ROUTING_DEVICES[0]); addWirelessDeviceIfNew(currentDevice);
} }
// find if media device enabled / available // find if media device enabled / available
final Pair<Boolean, Boolean> enabledAvailable = evaluateState(ROUTING_DEVICES[0]); final Pair<Boolean, Boolean> enabledAvailable = evaluateState(currentDevice);
boolean able = false; boolean able = false;
if (enabledAvailable.second) { if (enabledAvailable.second) {
// available for Spatial audio, check w/ effect // available for Spatial audio, check w/ effect
able = canBeSpatializedOnDevice(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, ROUTING_DEVICES); able = canBeSpatializedOnDevice(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, sRoutingDevices);
loglogi("onRoutingUpdated: can spatialize media 5.1:" + able loglogi("onRoutingUpdated: can spatialize media 5.1:" + able
+ " on device:" + ROUTING_DEVICES[0]); + " on device:" + currentDevice);
setDispatchAvailableState(able); setDispatchAvailableState(able);
} else { } else {
loglogi("onRoutingUpdated: device:" + ROUTING_DEVICES[0] loglogi("onRoutingUpdated: device:" + currentDevice
+ " not available for Spatial Audio"); + " not available for Spatial Audio");
setDispatchAvailableState(false); setDispatchAvailableState(false);
} }
@@ -400,10 +395,10 @@ public class SpatializerHelper {
boolean enabled = able && enabledAvailable.first; boolean enabled = able && enabledAvailable.first;
if (enabled) { if (enabled) {
loglogi("Enabling Spatial Audio since enabled for media device:" loglogi("Enabling Spatial Audio since enabled for media device:"
+ ROUTING_DEVICES[0]); + currentDevice);
} else { } else {
loglogi("Disabling Spatial Audio since disabled for media device:" loglogi("Disabling Spatial Audio since disabled for media device:"
+ ROUTING_DEVICES[0]); + currentDevice);
} }
if (mSpat != null) { if (mSpat != null) {
byte level = enabled ? (byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL byte level = enabled ? (byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL
@@ -736,9 +731,13 @@ public class SpatializerHelper {
} }
private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes, private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes,
@NonNull AudioFormat format, @NonNull AudioDeviceAttributes[] devices) { @NonNull AudioFormat format, @NonNull ArrayList<AudioDeviceAttributes> devices) {
if (isDeviceCompatibleWithSpatializationModes(devices[0])) { if (devices.isEmpty()) {
return AudioSystem.canBeSpatialized(attributes, format, devices); return false;
}
if (isDeviceCompatibleWithSpatializationModes(devices.get(0))) {
AudioDeviceAttributes[] devArray = new AudioDeviceAttributes[devices.size()];
return AudioSystem.canBeSpatialized(attributes, format, devices.toArray(devArray));
} }
return false; return false;
} }
@@ -1014,10 +1013,13 @@ public class SpatializerHelper {
logd("canBeSpatialized false due to usage:" + attributes.getUsage()); logd("canBeSpatialized false due to usage:" + attributes.getUsage());
return false; return false;
} }
AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1];
// going through adapter to take advantage of routing cache // going through adapter to take advantage of routing cache
mASA.getDevicesForAttributes( final ArrayList<AudioDeviceAttributes> devices = getRoutingDevices(attributes);
attributes, false /* forVolume */).toArray(devices); if (devices.isEmpty()) {
logloge("canBeSpatialized got no device for " + attributes);
return false;
}
final boolean able = canBeSpatializedOnDevice(attributes, format, devices); final boolean able = canBeSpatializedOnDevice(attributes, format, devices);
logd("canBeSpatialized usage:" + attributes.getUsage() logd("canBeSpatialized usage:" + attributes.getUsage()
+ " format:" + format.toLogFriendlyString() + " returning " + able); + " format:" + format.toLogFriendlyString() + " returning " + able);
@@ -1148,8 +1150,13 @@ public class SpatializerHelper {
logDeviceState(deviceState, "setHeadTrackerEnabled"); logDeviceState(deviceState, "setHeadTrackerEnabled");
// check current routing to see if it affects the headtracking mode // check current routing to see if it affects the headtracking mode
if (ROUTING_DEVICES[0] != null && ROUTING_DEVICES[0].getType() == ada.getType() if (sRoutingDevices.isEmpty()) {
&& ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) { logloge("setHeadTrackerEnabled: no device, bailing");
return;
}
final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
if (currentDevice.getType() == ada.getType()
&& currentDevice.getAddress().equals(ada.getAddress())) {
setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled
: Spatializer.HEAD_TRACKING_MODE_DISABLED); : Spatializer.HEAD_TRACKING_MODE_DISABLED);
if (enabled && !mHeadTrackerAvailable) { if (enabled && !mHeadTrackerAvailable) {
@@ -1706,10 +1713,11 @@ public class SpatializerHelper {
private int getHeadSensorHandleUpdateTracker() { private int getHeadSensorHandleUpdateTracker() {
int headHandle = -1; int headHandle = -1;
final AudioDeviceAttributes currentDevice = ROUTING_DEVICES[0]; if (sRoutingDevices.isEmpty()) {
if (currentDevice == null) { logloge("getHeadSensorHandleUpdateTracker: no device, no head tracker");
return headHandle; return headHandle;
} }
final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(currentDevice); UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(currentDevice);
// We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion // We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion
// with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR // with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR
@@ -1743,6 +1751,23 @@ public class SpatializerHelper {
return screenHandle; return screenHandle;
} }
/**
* Returns routing for the given attributes
* @param aa AudioAttributes whose routing is being queried
* @return a non-null never-empty list of devices. If the routing query failed, the list
* will contain null.
*/
private @NonNull ArrayList<AudioDeviceAttributes> getRoutingDevices(AudioAttributes aa) {
final ArrayList<AudioDeviceAttributes> devices = mASA.getDevicesForAttributes(
aa, false /* forVolume */);
for (AudioDeviceAttributes ada : devices) {
if (ada == null) {
// invalid entry, reject this routing query by returning an empty list
return new ArrayList<>(0);
}
}
return devices;
}
private static void loglogi(String msg) { private static void loglogi(String msg) {
AudioService.sSpatialLogger.loglogi(msg, TAG); AudioService.sSpatialLogger.loglogi(msg, TAG);
@@ -1759,4 +1784,13 @@ public class SpatializerHelper {
/*package*/ void clearSADevices() { /*package*/ void clearSADevices() {
mSADevices.clear(); mSADevices.clear();
} }
/*package*/ synchronized void forceStateForTest(int state) {
mState = state;
}
/*package*/ synchronized void initForTest(boolean hasBinaural, boolean hasTransaural) {
mBinauralSupported = hasBinaural;
mTransauralSupported = hasTransaural;
}
} }

View File

@@ -17,12 +17,17 @@ package com.android.server.audio;
import com.android.server.audio.SpatializerHelper.SADeviceState; import com.android.server.audio.SpatializerHelper.SADeviceState;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes; import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo; import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioSystem; import android.media.AudioSystem;
import android.util.Log; import android.util.Log;
@@ -36,6 +41,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Spy; import org.mockito.Spy;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@MediumTest @MediumTest
@@ -49,16 +55,35 @@ public class SpatializerHelperTest {
@Mock private AudioService mMockAudioService; @Mock private AudioService mMockAudioService;
@Spy private AudioSystemAdapter mSpyAudioSystem; @Spy private AudioSystemAdapter mSpyAudioSystem;
@Mock private AudioSystemAdapter mMockAudioSystem;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
mMockAudioService = mock(AudioService.class); mMockAudioService = mock(AudioService.class);
mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); }
mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem, /**
* Initializes mSpatHelper, the SpatizerHelper instance under test, to use the mock or spy
* AudioSystemAdapter
* @param useSpyAudioSystem true to use the spy adapter, mSpyAudioSystem, or false to use
* the mock adapter, mMockAudioSystem.
*/
private void setUpSpatHelper(boolean useSpyAudioSystem) {
final AudioSystemAdapter asAdapter;
if (useSpyAudioSystem) {
mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
asAdapter = mSpyAudioSystem;
mMockAudioSystem = null;
} else {
mSpyAudioSystem = null;
mMockAudioSystem = mock(NoOpAudioSystemAdapter.class);
asAdapter = mMockAudioSystem;
}
mSpatHelper = new SpatializerHelper(mMockAudioService, asAdapter,
true /*binauralEnabledDefault*/, true /*binauralEnabledDefault*/,
true /*transauralEnabledDefault*/, true /*transauralEnabledDefault*/,
false /*headTrackingEnabledDefault*/); false /*headTrackingEnabledDefault*/);
} }
/** /**
@@ -68,6 +93,7 @@ public class SpatializerHelperTest {
*/ */
@Test @Test
public void testSADeviceStateNullAddressCtor() throws Exception { public void testSADeviceStateNullAddressCtor() throws Exception {
setUpSpatHelper(true /*useSpyAudioSystem*/);
try { try {
SADeviceState devState = new SADeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null); SADeviceState devState = new SADeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
devState = new SADeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, null); devState = new SADeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, null);
@@ -78,6 +104,7 @@ public class SpatializerHelperTest {
@Test @Test
public void testSADeviceStateStringSerialization() throws Exception { public void testSADeviceStateStringSerialization() throws Exception {
Log.i(TAG, "starting testSADeviceStateStringSerialization"); Log.i(TAG, "starting testSADeviceStateStringSerialization");
setUpSpatHelper(true /*useSpyAudioSystem*/);
final SADeviceState devState = new SADeviceState( final SADeviceState devState = new SADeviceState(
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "bla"); AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "bla");
devState.mHasHeadTracker = false; devState.mHasHeadTracker = false;
@@ -93,6 +120,7 @@ public class SpatializerHelperTest {
@Test @Test
public void testSADeviceSettings() throws Exception { public void testSADeviceSettings() throws Exception {
Log.i(TAG, "starting testSADeviceSettings"); Log.i(TAG, "starting testSADeviceSettings");
setUpSpatHelper(true /*useSpyAudioSystem*/);
final AudioDeviceAttributes dev1 = final AudioDeviceAttributes dev1 =
new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""); new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, "");
final AudioDeviceAttributes dev2 = final AudioDeviceAttributes dev2 =
@@ -143,4 +171,34 @@ public class SpatializerHelperTest {
Log.i(TAG, "device settingsRestored: " + settingsRestored); Log.i(TAG, "device settingsRestored: " + settingsRestored);
Assert.assertEquals(settings, settingsRestored); Assert.assertEquals(settings, settingsRestored);
} }
/**
* Test that null devices for routing do not break canBeSpatialized
* @throws Exception
*/
@Test
public void testNoRoutingCanBeSpatialized() throws Exception {
Log.i(TAG, "Starting testNoRoutingCanBeSpatialized");
setUpSpatHelper(false /*useSpyAudioSystem*/);
mSpatHelper.forceStateForTest(SpatializerHelper.STATE_ENABLED_AVAILABLE);
final ArrayList<AudioDeviceAttributes> emptyList = new ArrayList<>(0);
final ArrayList<AudioDeviceAttributes> listWithNull = new ArrayList<>(1);
listWithNull.add(null);
final AudioAttributes media = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA).build();
final AudioFormat spatialFormat = new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1).build();
when(mMockAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
.thenReturn(emptyList);
Assert.assertFalse("can be spatialized on empty routing",
mSpatHelper.canBeSpatialized(media, spatialFormat));
when(mMockAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
.thenReturn(listWithNull);
Assert.assertFalse("can be spatialized on null routing",
mSpatHelper.canBeSpatialized(media, spatialFormat));
}
} }