diff options
| -rw-r--r-- | services/core/java/com/android/server/audio/SpatializerHelper.java | 110 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java | 62 |
2 files changed, 132 insertions, 40 deletions
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index a9e7d4b0bac8..c9cdba970ccb 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -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; + } } diff --git a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java index dea31d764522..3ad24de4cdca 100644 --- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java @@ -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)); + } } |