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

This commit is contained in:
TreeHugger Robot
2023-03-24 16:43:50 +00:00
committed by Android (Google) Code Review
2 changed files with 132 additions and 40 deletions

View File

@@ -106,12 +106,12 @@ public class SpatializerHelper {
};
// Spatializer state machine
private static final int STATE_UNINITIALIZED = 0;
private static final int STATE_NOT_SUPPORTED = 1;
private static final int STATE_DISABLED_UNAVAILABLE = 3;
private static final int STATE_ENABLED_UNAVAILABLE = 4;
private static final int STATE_ENABLED_AVAILABLE = 5;
private static final int STATE_DISABLED_AVAILABLE = 6;
/*package*/ static final int STATE_UNINITIALIZED = 0;
/*package*/ static final int STATE_NOT_SUPPORTED = 1;
/*package*/ static final int STATE_DISABLED_UNAVAILABLE = 3;
/*package*/ static final int STATE_ENABLED_UNAVAILABLE = 4;
/*package*/ static final int STATE_ENABLED_AVAILABLE = 5;
/*package*/ static final int STATE_DISABLED_AVAILABLE = 6;
private int mState = STATE_UNINITIALIZED;
private boolean mFeatureEnabled = false;
@@ -147,9 +147,9 @@ public class SpatializerHelper {
.setSampleRate(48000)
.setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
.build();
// device array to store the routing for the default attributes and format, size 1 because
// media is never expected to be duplicated
private static final AudioDeviceAttributes[] ROUTING_DEVICES = new AudioDeviceAttributes[1];
// device array to store the routing for the default attributes and format, initialized to
// an empty list as routing hasn't been established yet
private static ArrayList<AudioDeviceAttributes> sRoutingDevices = new ArrayList<>(0);
//---------------------------------------------------------------
// audio device compatibility / enabled
@@ -184,11 +184,6 @@ public class SpatializerHelper {
SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault;
}
synchronized void initForTest(boolean hasBinaural, boolean hasTransaural) {
mBinauralSupported = hasBinaural;
mTransauralSupported = hasTransaural;
}
synchronized void init(boolean effectExpected, @Nullable String settings) {
loglogi("init effectExpected=" + effectExpected);
if (!effectExpected) {
@@ -322,8 +317,7 @@ public class SpatializerHelper {
return;
}
mState = STATE_DISABLED_UNAVAILABLE;
mASA.getDevicesForAttributes(
DEFAULT_ATTRIBUTES, false /* forVolume */).toArray(ROUTING_DEVICES);
sRoutingDevices = getRoutingDevices(DEFAULT_ATTRIBUTES);
// note at this point mSpat is still not instantiated
}
@@ -365,34 +359,35 @@ public class SpatializerHelper {
case STATE_DISABLED_AVAILABLE:
break;
}
mASA.getDevicesForAttributes(
DEFAULT_ATTRIBUTES, false /* forVolume */).toArray(ROUTING_DEVICES);
sRoutingDevices = getRoutingDevices(DEFAULT_ATTRIBUTES);
// check validity of routing information
if (ROUTING_DEVICES[0] == null) {
logloge("onRoutingUpdated: device is null, no Spatial Audio");
if (sRoutingDevices.isEmpty()) {
logloge("onRoutingUpdated: no device, no Spatial Audio");
setDispatchAvailableState(false);
// not changing the spatializer level as this is likely a transient state
return;
}
final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
// is media routed to a new device?
if (isWireless(ROUTING_DEVICES[0].getType())) {
addWirelessDeviceIfNew(ROUTING_DEVICES[0]);
if (isWireless(currentDevice.getType())) {
addWirelessDeviceIfNew(currentDevice);
}
// 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;
if (enabledAvailable.second) {
// 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
+ " on device:" + ROUTING_DEVICES[0]);
+ " on device:" + currentDevice);
setDispatchAvailableState(able);
} else {
loglogi("onRoutingUpdated: device:" + ROUTING_DEVICES[0]
loglogi("onRoutingUpdated: device:" + currentDevice
+ " not available for Spatial Audio");
setDispatchAvailableState(false);
}
@@ -400,10 +395,10 @@ public class SpatializerHelper {
boolean enabled = able && enabledAvailable.first;
if (enabled) {
loglogi("Enabling Spatial Audio since enabled for media device:"
+ ROUTING_DEVICES[0]);
+ currentDevice);
} else {
loglogi("Disabling Spatial Audio since disabled for media device:"
+ ROUTING_DEVICES[0]);
+ currentDevice);
}
if (mSpat != null) {
byte level = enabled ? (byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL
@@ -736,9 +731,13 @@ public class SpatializerHelper {
}
private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes,
@NonNull AudioFormat format, @NonNull AudioDeviceAttributes[] devices) {
if (isDeviceCompatibleWithSpatializationModes(devices[0])) {
return AudioSystem.canBeSpatialized(attributes, format, devices);
@NonNull AudioFormat format, @NonNull ArrayList<AudioDeviceAttributes> devices) {
if (devices.isEmpty()) {
return false;
}
if (isDeviceCompatibleWithSpatializationModes(devices.get(0))) {
AudioDeviceAttributes[] devArray = new AudioDeviceAttributes[devices.size()];
return AudioSystem.canBeSpatialized(attributes, format, devices.toArray(devArray));
}
return false;
}
@@ -1014,10 +1013,13 @@ public class SpatializerHelper {
logd("canBeSpatialized false due to usage:" + attributes.getUsage());
return false;
}
AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1];
// going through adapter to take advantage of routing cache
mASA.getDevicesForAttributes(
attributes, false /* forVolume */).toArray(devices);
final ArrayList<AudioDeviceAttributes> devices = getRoutingDevices(attributes);
if (devices.isEmpty()) {
logloge("canBeSpatialized got no device for " + attributes);
return false;
}
final boolean able = canBeSpatializedOnDevice(attributes, format, devices);
logd("canBeSpatialized usage:" + attributes.getUsage()
+ " format:" + format.toLogFriendlyString() + " returning " + able);
@@ -1148,8 +1150,13 @@ public class SpatializerHelper {
logDeviceState(deviceState, "setHeadTrackerEnabled");
// check current routing to see if it affects the headtracking mode
if (ROUTING_DEVICES[0] != null && ROUTING_DEVICES[0].getType() == ada.getType()
&& ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) {
if (sRoutingDevices.isEmpty()) {
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
: Spatializer.HEAD_TRACKING_MODE_DISABLED);
if (enabled && !mHeadTrackerAvailable) {
@@ -1706,10 +1713,11 @@ public class SpatializerHelper {
private int getHeadSensorHandleUpdateTracker() {
int headHandle = -1;
final AudioDeviceAttributes currentDevice = ROUTING_DEVICES[0];
if (currentDevice == null) {
if (sRoutingDevices.isEmpty()) {
logloge("getHeadSensorHandleUpdateTracker: no device, no head tracker");
return headHandle;
}
final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(currentDevice);
// We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion
// with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR
@@ -1743,6 +1751,23 @@ public class SpatializerHelper {
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) {
AudioService.sSpatialLogger.loglogi(msg, TAG);
@@ -1759,4 +1784,13 @@ public class SpatializerHelper {
/*package*/ void clearSADevices() {
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 static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioSystem;
import android.util.Log;
@@ -36,6 +41,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import java.util.ArrayList;
import java.util.List;
@MediumTest
@@ -49,16 +55,35 @@ public class SpatializerHelperTest {
@Mock private AudioService mMockAudioService;
@Spy private AudioSystemAdapter mSpyAudioSystem;
@Mock private AudioSystemAdapter mMockAudioSystem;
@Before
public void setUp() throws Exception {
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 /*transauralEnabledDefault*/,
false /*headTrackingEnabledDefault*/);
}
/**
@@ -68,6 +93,7 @@ public class SpatializerHelperTest {
*/
@Test
public void testSADeviceStateNullAddressCtor() throws Exception {
setUpSpatHelper(true /*useSpyAudioSystem*/);
try {
SADeviceState devState = new SADeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
devState = new SADeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, null);
@@ -78,6 +104,7 @@ public class SpatializerHelperTest {
@Test
public void testSADeviceStateStringSerialization() throws Exception {
Log.i(TAG, "starting testSADeviceStateStringSerialization");
setUpSpatHelper(true /*useSpyAudioSystem*/);
final SADeviceState devState = new SADeviceState(
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "bla");
devState.mHasHeadTracker = false;
@@ -93,6 +120,7 @@ public class SpatializerHelperTest {
@Test
public void testSADeviceSettings() throws Exception {
Log.i(TAG, "starting testSADeviceSettings");
setUpSpatHelper(true /*useSpyAudioSystem*/);
final AudioDeviceAttributes dev1 =
new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, "");
final AudioDeviceAttributes dev2 =
@@ -143,4 +171,34 @@ public class SpatializerHelperTest {
Log.i(TAG, "device settingsRestored: " + 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));
}
}