Merge "Adding support for IAudioControl@2.0" into rvc-dev
diff --git a/service/Android.bp b/service/Android.bp
index ab6e4ce..50f0f41 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -34,6 +34,7 @@
"android.car.watchdoglib",
"android.hidl.base-V1.0-java",
"android.hardware.automotive.audiocontrol-V1.0-java",
+ "android.hardware.automotive.audiocontrol-V2.0-java",
"android.hardware.automotive.vehicle-V2.0-java",
"android.hardware.health-V1.0-java",
"android.hardware.health-V2.0-java",
diff --git a/service/src/com/android/car/audio/AudioControlWrapper.java b/service/src/com/android/car/audio/AudioControlWrapper.java
new file mode 100644
index 0000000..e23140b
--- /dev/null
+++ b/service/src/com/android/car/audio/AudioControlWrapper.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 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 com.android.car.audio;
+
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.car.audio.CarAudioContext.AudioContext;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.util.NoSuchElementException;
+
+/**
+ * AudioControlWrapper wraps IAudioControl HAL interface, handling version specific support so that
+ * the rest of CarAudioService doesn't need to know about it.
+ */
+final class AudioControlWrapper {
+ private static final String TAG = AudioControlWrapper.class.getSimpleName();
+ @Nullable
+ private final android.hardware.automotive.audiocontrol.V1_0.IAudioControl mAudioControlV1;
+ @Nullable
+ private final android.hardware.automotive.audiocontrol.V2_0.IAudioControl mAudioControlV2;
+
+ static AudioControlWrapper newAudioControl() {
+ android.hardware.automotive.audiocontrol.V1_0.IAudioControl audioControlV1 = null;
+ android.hardware.automotive.audiocontrol.V2_0.IAudioControl audioControlV2 = null;
+ try {
+ audioControlV2 = android.hardware.automotive.audiocontrol.V2_0.IAudioControl
+ .getService(true);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Failed to get IAudioControl V2 service", e);
+ } catch (NoSuchElementException e) {
+ Log.d(TAG, "IAudioControl@V2.0 not in the manifest");
+ }
+
+ try {
+ audioControlV1 = android.hardware.automotive.audiocontrol.V1_0.IAudioControl
+ .getService(true);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Failed to get IAudioControl V1 service", e);
+ } catch (NoSuchElementException e) {
+ Log.d(TAG, "IAudioControl@V1.0 not in the manifest");
+ }
+
+ return new AudioControlWrapper(audioControlV1, audioControlV2);
+ }
+
+ @VisibleForTesting
+ AudioControlWrapper(
+ @Nullable android.hardware.automotive.audiocontrol.V1_0.IAudioControl audioControlV1,
+ @Nullable android.hardware.automotive.audiocontrol.V2_0.IAudioControl audioControlV2) {
+ mAudioControlV1 = audioControlV1;
+ mAudioControlV2 = audioControlV2;
+ checkAudioControl();
+ }
+
+ private void checkAudioControl() {
+ if (mAudioControlV2 != null && mAudioControlV1 != null) {
+ Log.w(TAG, "Both versions of IAudioControl are present, defaulting to V2");
+ } else if (mAudioControlV2 == null && mAudioControlV1 == null) {
+ throw new IllegalStateException("No version of AudioControl HAL in the manifest");
+ } else if (mAudioControlV1 != null) {
+ Log.w(TAG, "IAudioControl@V1.0 is deprecated. Consider upgrading to V2.0");
+ }
+ }
+
+ void setFadeTowardFront(float value) {
+ try {
+ if (mAudioControlV2 != null) {
+ mAudioControlV2.setFadeTowardFront(value);
+ } else {
+ mAudioControlV1.setFadeTowardFront(value);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "setFadeTowardFront failed", e);
+ }
+ }
+
+ void setBalanceTowardRight(float value) {
+ try {
+ if (mAudioControlV2 != null) {
+ mAudioControlV2.setBalanceTowardRight(value);
+ } else {
+ mAudioControlV1.setBalanceTowardRight(value);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "setBalanceTowardRight failed", e);
+ }
+ }
+
+ /**
+ * Gets the bus associated with CarAudioContext
+ *
+ * <p>This API is used along with car_volume_groups.xml to configure volume groups and routing.
+ *
+ * @param audioContext CarAudioContext to get a context for
+ * @return int bus number. Should be part of the prefix for the device's address. For example,
+ * bus001_media would be bus 1.
+ * @deprecated Volume and routing configuration has been replaced by
+ * car_audio_configuration.xml. Starting with IAudioControl@V2.0, getBusForContext is no longer
+ * supported.
+ */
+ @Deprecated
+ int getBusForContext(@AudioContext int audioContext) {
+ Preconditions.checkState(mAudioControlV2 == null,
+ "IAudioControl#getBusForContext no longer supported beyond V1.0");
+
+ try {
+ return mAudioControlV1.getBusForContext(audioContext);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to query IAudioControl HAL to get bus for context", e);
+ throw new IllegalStateException("Failed to query IAudioControl#getBusForContext", e);
+ }
+ }
+}
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 807d711..5733f6d 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -30,8 +30,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
-import android.hardware.automotive.audiocontrol.V1_0.IAudioControl;
import android.media.AudioAttributes;
import android.media.AudioAttributes.AttributeSystemUsage;
import android.media.AudioAttributes.AttributeUsage;
@@ -50,7 +48,6 @@
import android.media.audiopolicy.AudioPolicy;
import android.os.IBinder;
import android.os.Looper;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -79,7 +76,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -123,6 +119,7 @@
private final boolean mUseDynamicRouting;
private final boolean mPersistMasterMuteState;
private final CarVolumeSettings mCarVolumeSettings;
+ private AudioControlWrapper mAudioControlWrapper;
private CarOccupantZoneService mOccupantZoneService;
@@ -233,7 +230,7 @@
Car car = new Car(mContext, /* service= */null, /* handler= */ null);
mOccupantZoneManager = new CarOccupantZoneManager(car, mOccupantZoneService);
if (mUseDynamicRouting) {
- setupDynamicRouting();
+ setupDynamicRoutingLocked();
} else {
Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not enabled, run in legacy mode");
setupLegacyVolumeChangedListener();
@@ -438,7 +435,8 @@
AudioManager.GET_DEVICES_INPUTS);
}
- private CarAudioZone[] loadCarAudioConfiguration(List<CarAudioDeviceInfo> carAudioDeviceInfos) {
+ private CarAudioZone[] loadCarAudioConfigurationLocked(
+ List<CarAudioDeviceInfo> carAudioDeviceInfos) {
AudioDeviceInfo[] inputDevices = getAllInputDevices();
try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mContext, inputStream,
@@ -451,37 +449,33 @@
}
}
- private CarAudioZone[] loadVolumeGroupConfigurationWithAudioControl(
+ private CarAudioZone[] loadVolumeGroupConfigurationWithAudioControlLocked(
List<CarAudioDeviceInfo> carAudioDeviceInfos) {
- // In legacy mode, context -> bus mapping is done by querying IAudioControl HAL.
- final IAudioControl audioControl = getAudioControl();
- if (audioControl == null) {
- throw new RuntimeException(
- "Dynamic routing requested but audioControl HAL not available");
- }
+ AudioControlWrapper audioControlWrapper = getAudioControlWrapperLocked();
CarAudioZonesHelperLegacy legacyHelper = new CarAudioZonesHelperLegacy(mContext,
- R.xml.car_volume_groups, carAudioDeviceInfos, audioControl);
+ R.xml.car_volume_groups, carAudioDeviceInfos, audioControlWrapper);
return legacyHelper.loadAudioZones();
}
- private void loadCarAudioZones() {
+ private void loadCarAudioZonesLocked() {
List<CarAudioDeviceInfo> carAudioDeviceInfos = generateCarAudioDeviceInfos();
mCarAudioConfigurationPath = getAudioConfigurationPath();
if (mCarAudioConfigurationPath != null) {
- mCarAudioZones = loadCarAudioConfiguration(carAudioDeviceInfos);
+ mCarAudioZones = loadCarAudioConfigurationLocked(carAudioDeviceInfos);
} else {
- mCarAudioZones = loadVolumeGroupConfigurationWithAudioControl(carAudioDeviceInfos);
+ mCarAudioZones = loadVolumeGroupConfigurationWithAudioControlLocked(
+ carAudioDeviceInfos);
}
CarAudioZonesValidator.validate(mCarAudioZones);
}
- private void setupDynamicRouting() {
+ private void setupDynamicRoutingLocked() {
final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
builder.setLooper(Looper.getMainLooper());
- loadCarAudioZones();
+ loadCarAudioZonesLocked();
for (CarAudioZone zone : mCarAudioZones) {
// Ensure HAL gets our initial value
@@ -555,14 +549,7 @@
public void setFadeTowardFront(float value) {
synchronized (mImplLock) {
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
- final IAudioControl audioControlHal = getAudioControl();
- if (audioControlHal != null) {
- try {
- audioControlHal.setFadeTowardFront(value);
- } catch (RemoteException e) {
- Log.e(CarLog.TAG_AUDIO, "setFadeTowardFront failed", e);
- }
- }
+ getAudioControlWrapperLocked().setFadeTowardFront(value);
}
}
@@ -570,14 +557,7 @@
public void setBalanceTowardRight(float value) {
synchronized (mImplLock) {
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
- final IAudioControl audioControlHal = getAudioControl();
- if (audioControlHal != null) {
- try {
- audioControlHal.setBalanceTowardRight(value);
- } catch (RemoteException e) {
- Log.e(CarLog.TAG_AUDIO, "setBalanceTowardRight failed", e);
- }
- }
+ getAudioControlWrapperLocked().setBalanceTowardRight(value);
}
}
@@ -904,7 +884,7 @@
Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
"zoneId (" + zoneId + ")");
int contextForUsage = CarAudioContext.getContextForUsage(usage);
- Preconditions.checkArgument(contextForUsage != ContextNumber.INVALID,
+ Preconditions.checkArgument(contextForUsage != CarAudioContext.INVALID,
"Invalid audio attribute usage %d", usage);
return mCarAudioZones[zoneId].getAddressForContext(contextForUsage);
}
@@ -1172,16 +1152,11 @@
return mAudioZoneIdToUserIdMapping.get(audioZoneId, UserHandle.USER_NULL);
}
- @Nullable
- private static IAudioControl getAudioControl() {
- try {
- return IAudioControl.getService();
- } catch (RemoteException e) {
- Log.e(CarLog.TAG_AUDIO, "Failed to get IAudioControl service", e);
- } catch (NoSuchElementException e) {
- Log.e(CarLog.TAG_AUDIO, "IAudioControl service not registered yet");
+ private AudioControlWrapper getAudioControlWrapperLocked() {
+ if (mAudioControlWrapper == null) {
+ mAudioControlWrapper = AudioControlWrapper.newAudioControl();
}
- return null;
+ return mAudioControlWrapper;
}
boolean isAudioZoneIdValid(int zoneId) {
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
index 947a886..23c11bf 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
@@ -23,8 +23,6 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
-import android.hardware.automotive.audiocontrol.V1_0.IAudioControl;
-import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
@@ -60,12 +58,12 @@
CarAudioZonesHelperLegacy(Context context, @XmlRes int xmlConfiguration,
@NonNull List<CarAudioDeviceInfo> carAudioDeviceInfos,
- @NonNull IAudioControl audioControl) {
+ @NonNull AudioControlWrapper audioControlWrapper) {
mContext = context;
mXmlConfiguration = xmlConfiguration;
mBusToCarAudioDeviceInfo = generateBusToCarAudioDeviceInfo(carAudioDeviceInfos);
- mLegacyAudioContextToBus = loadBusesForLegacyContexts(audioControl);
+ mLegacyAudioContextToBus = loadBusesForLegacyContexts(audioControlWrapper);
}
/* Loads mapping from {@link CarAudioContext} values to bus numbers
@@ -74,23 +72,18 @@
* contexts are those defined as part of
* {@code android.hardware.automotive.audiocontrol.V1_0.ContextNumber}
*
- * @param audioControl handle for IAudioControl HAL to fetch bus numbers from
+ * @param audioControl wrapper for IAudioControl HAL interface.
* @return SparseIntArray mapping from {@link CarAudioContext} to bus number.
*/
- private static SparseIntArray loadBusesForLegacyContexts(@NonNull IAudioControl audioControl) {
+ private static SparseIntArray loadBusesForLegacyContexts(
+ @NonNull AudioControlWrapper audioControlWrapper) {
SparseIntArray contextToBus = new SparseIntArray();
- try {
- for (int legacyContext : LEGACY_CONTEXTS) {
- int bus = audioControl.getBusForContext(legacyContext);
- validateBusNumber(legacyContext, bus);
- contextToBus.put(legacyContext, bus);
- }
- } catch (RemoteException e) {
- Log.e(CarLog.TAG_AUDIO, "Failed to query IAudioControl HAL", e);
- e.rethrowAsRuntimeException();
+ for (int legacyContext : LEGACY_CONTEXTS) {
+ int bus = audioControlWrapper.getBusForContext(legacyContext);
+ validateBusNumber(legacyContext, bus);
+ contextToBus.put(legacyContext, bus);
}
-
return contextToBus;
}
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java
index 4ff4c4b..9428681 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperLegacyTest.java
@@ -23,7 +23,6 @@
import android.annotation.XmlRes;
import android.content.Context;
-import android.hardware.automotive.audiocontrol.V1_0.IAudioControl;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -50,7 +49,7 @@
public final MockitoRule rule = MockitoJUnit.rule();
@Mock
- private IAudioControl mMockAudioControl;
+ private AudioControlWrapper mMockAudioControlWrapper;
private static final int INVALID_BUS = -1;
private final Context mContext = ApplicationProvider.getApplicationContext();
@@ -62,7 +61,7 @@
RuntimeException exception = expectThrows(RuntimeException.class,
() -> new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
- carAudioDeviceInfos, mMockAudioControl));
+ carAudioDeviceInfos, mMockAudioControlWrapper));
assertThat(exception.getMessage()).contains("Two addresses map to same bus number:");
}
@@ -71,11 +70,11 @@
public void constructor_throwsIfLegacyContextNotAssignedToBus() throws Exception {
List<CarAudioDeviceInfo> carAudioDeviceInfos = getValidCarAudioDeviceInfos();
- when(mMockAudioControl.getBusForContext(anyInt())).thenReturn(INVALID_BUS);
+ when(mMockAudioControlWrapper.getBusForContext(anyInt())).thenReturn(INVALID_BUS);
RuntimeException exception = expectThrows(RuntimeException.class,
() -> new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
- carAudioDeviceInfos, mMockAudioControl));
+ carAudioDeviceInfos, mMockAudioControlWrapper));
assertThat(exception.getMessage()).contains("Invalid bus -1 was associated with context");
}
@@ -83,10 +82,10 @@
@Test
public void loadAudioZones_succeeds() throws Exception {
List<CarAudioDeviceInfo> carAudioDeviceInfos = getValidCarAudioDeviceInfos();
- when(mMockAudioControl.getBusForContext(anyInt())).thenReturn(1);
+ when(mMockAudioControlWrapper.getBusForContext(anyInt())).thenReturn(1);
CarAudioZonesHelperLegacy helper = new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
- carAudioDeviceInfos, mMockAudioControl);
+ carAudioDeviceInfos, mMockAudioControlWrapper);
CarAudioZone[] zones = helper.loadAudioZones();
@@ -97,10 +96,10 @@
public void loadAudioZones_parsesAllVolumeGroups() throws Exception {
List<CarAudioDeviceInfo> carAudioDeviceInfos = getValidCarAudioDeviceInfos();
- when(mMockAudioControl.getBusForContext(anyInt())).thenReturn(1);
+ when(mMockAudioControlWrapper.getBusForContext(anyInt())).thenReturn(1);
CarAudioZonesHelperLegacy helper = new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
- carAudioDeviceInfos, mMockAudioControl);
+ carAudioDeviceInfos, mMockAudioControlWrapper);
CarAudioZone[] zones = helper.loadAudioZones();
CarVolumeGroup[] volumeGroups = zones[0].getVolumeGroups();
@@ -111,11 +110,11 @@
public void loadAudioZones_associatesLegacyContextsWithCorrectBuses() throws Exception {
List<CarAudioDeviceInfo> carAudioDeviceInfos = getValidCarAudioDeviceInfos();
- when(mMockAudioControl.getBusForContext(anyInt())).thenReturn(2);
- when(mMockAudioControl.getBusForContext(CarAudioContext.MUSIC)).thenReturn(1);
+ when(mMockAudioControlWrapper.getBusForContext(anyInt())).thenReturn(2);
+ when(mMockAudioControlWrapper.getBusForContext(CarAudioContext.MUSIC)).thenReturn(1);
CarAudioZonesHelperLegacy helper = new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
- carAudioDeviceInfos, mMockAudioControl);
+ carAudioDeviceInfos, mMockAudioControlWrapper);
CarAudioZone[] zones = helper.loadAudioZones();
@@ -137,12 +136,12 @@
@Test
public void loadAudioZones_associatesNonLegacyContextsWithMediaBus() throws Exception {
List<CarAudioDeviceInfo> carAudioDeviceInfos = getValidCarAudioDeviceInfos();
- when(mMockAudioControl.getBusForContext(anyInt())).thenReturn(2);
- when(mMockAudioControl.getBusForContext(CarAudioService.DEFAULT_AUDIO_CONTEXT))
+ when(mMockAudioControlWrapper.getBusForContext(anyInt())).thenReturn(2);
+ when(mMockAudioControlWrapper.getBusForContext(CarAudioService.DEFAULT_AUDIO_CONTEXT))
.thenReturn(1);
CarAudioZonesHelperLegacy helper = new CarAudioZonesHelperLegacy(mContext, mCarVolumeGroups,
- carAudioDeviceInfos, mMockAudioControl);
+ carAudioDeviceInfos, mMockAudioControlWrapper);
CarAudioZone[] zones = helper.loadAudioZones();
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/AudioControlWrapperTest.java b/tests/carservice_unit_test/src/com/android/car/audio/AudioControlWrapperTest.java
new file mode 100644
index 0000000..b54f002
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/AudioControlWrapperTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2020 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 com.android.car.audio;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(AndroidJUnit4.class)
+public class AudioControlWrapperTest {
+ private static final float FADE_VALUE = 5;
+ private static final float BALANCE_VALUE = 6;
+ private static final int CONTEXT_NUMBER = 3;
+
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ @Mock
+ android.hardware.automotive.audiocontrol.V1_0.IAudioControl mAudioControlV1;
+ @Mock
+ android.hardware.automotive.audiocontrol.V2_0.IAudioControl mAudioControlV2;
+
+ @Test
+ public void constructor_throwsIfBothVersionsAreNull() throws Exception {
+ assertThrows(IllegalStateException.class, () -> new AudioControlWrapper(null, null));
+ }
+
+ @Test
+ public void constructor_succeedsWithOneVersion() throws Exception {
+ new AudioControlWrapper(null, mAudioControlV2);
+ }
+
+ @Test
+ public void setFadeTowardFront_withBothVersions_defaultsToV2() throws Exception {
+ AudioControlWrapper audioControlWrapper = new AudioControlWrapper(mAudioControlV1,
+ mAudioControlV2);
+ audioControlWrapper.setFadeTowardFront(FADE_VALUE);
+
+ verify(mAudioControlV2).setFadeTowardFront(FADE_VALUE);
+ verify(mAudioControlV1, never()).setFadeTowardFront(anyFloat());
+ }
+
+ @Test
+ public void setFadeTowardFront_withJustV1_succeeds() throws Exception {
+ AudioControlWrapper audioControlWrapper = new AudioControlWrapper(mAudioControlV1, null);
+ audioControlWrapper.setFadeTowardFront(FADE_VALUE);
+
+ verify(mAudioControlV1).setFadeTowardFront(FADE_VALUE);
+ }
+
+ @Test
+ public void setBalanceTowardRight_withBothVersions_defaultsToV2() throws Exception {
+ AudioControlWrapper audioControlWrapper = new AudioControlWrapper(mAudioControlV1,
+ mAudioControlV2);
+ audioControlWrapper.setBalanceTowardRight(BALANCE_VALUE);
+
+ verify(mAudioControlV2).setBalanceTowardRight(BALANCE_VALUE);
+ verify(mAudioControlV1, never()).setBalanceTowardRight(anyFloat());
+ }
+
+ @Test
+ public void setBalanceTowardRight_withJustV1_succeeds() throws Exception {
+ AudioControlWrapper audioControlWrapper = new AudioControlWrapper(mAudioControlV1, null);
+ audioControlWrapper.setBalanceTowardRight(BALANCE_VALUE);
+
+ verify(mAudioControlV1).setBalanceTowardRight(BALANCE_VALUE);
+ }
+
+ @Test
+ public void getBusForContext_withV2Present_throws() {
+ AudioControlWrapper audioControlWrapper = new AudioControlWrapper(mAudioControlV1,
+ mAudioControlV2);
+ assertThrows(IllegalStateException.class,
+ () -> audioControlWrapper.getBusForContext(CONTEXT_NUMBER));
+ }
+
+ @Test
+ public void getBusForContext_withJustV1_returnsBusNumber() throws Exception {
+ AudioControlWrapper audioControlWrapper = new AudioControlWrapper(mAudioControlV1, null);
+ int busNumber = 1;
+ when(mAudioControlV1.getBusForContext(CONTEXT_NUMBER)).thenReturn(busNumber);
+
+ int actualBus = audioControlWrapper.getBusForContext(CONTEXT_NUMBER);
+ assertThat(actualBus).isEqualTo(busNumber);
+ }
+}