aboutsummaryrefslogtreecommitdiff
path: root/service/src/com
diff options
context:
space:
mode:
authorHongwei Wang <hwwang@google.com>2018-02-12 10:29:08 -0800
committerHongwei Wang <hwwang@google.com>2018-02-16 18:31:20 -0800
commit33707a9d6e5beb03c5ae010e661035f820cc4bb8 (patch)
tree0752a73150d6dfbfce5a9eaeaf7fb62bd36d7bbc /service/src/com
parent3e1b71d5709d3a844bcc2812dd993a6ce3cdd0a0 (diff)
downloadCar-33707a9d6e5beb03c5ae010e661035f820cc4bb8.tar.gz
Switches volume control to groupId based
New APIs added - getVolumeGroupForUsage(int) - getGroup{Min,Max,}Volume(int) - setGroupVolume(int) Old APIs removed - getUsage{Min,Max,}Volume - setUsageVolume Known issue - No volume sliders in Mojave board since there is no volume group configured Adds also the validation pass for volume groups configuration Bug: 72555604 Test: run Car Settings in emulator Change-Id: I2507a45f2771e26fedc5cfbdb017023fa3d67d46
Diffstat (limited to 'service/src/com')
-rw-r--r--service/src/com/android/car/CarAudioDeviceInfo.java (renamed from service/src/com/android/car/AudioDeviceInfoState.java)119
-rw-r--r--service/src/com/android/car/CarAudioService.java317
-rw-r--r--service/src/com/android/car/CarVolumeGroup.java162
-rw-r--r--service/src/com/android/car/CarVolumeGroupsHelper.java21
4 files changed, 447 insertions, 172 deletions
diff --git a/service/src/com/android/car/AudioDeviceInfoState.java b/service/src/com/android/car/CarAudioDeviceInfo.java
index ca380238ed..b8b5e82e54 100644
--- a/service/src/com/android/car/AudioDeviceInfoState.java
+++ b/service/src/com/android/car/CarAudioDeviceInfo.java
@@ -15,6 +15,7 @@
*/
package com.android.car;
+import android.annotation.Nullable;
import android.car.media.CarAudioManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -22,15 +23,20 @@ import android.media.AudioDeviceInfo;
import android.media.AudioDevicePort;
import android.media.AudioFormat;
import android.media.AudioGain;
+import android.media.AudioGainConfig;
+import android.media.AudioManager;
+import android.media.AudioPort;
import android.provider.Settings;
+import android.util.Log;
import com.android.internal.util.Preconditions;
/**
- * This class holds the min and max gain index translated from min / max / step values in
- * gain control. Also tracks the current gain index on a certain {@link AudioDevicePort}.
+ * A helper class wraps {@link AudioDeviceInfo}, translates the min / max/ step gain values
+ * in gain controller to gain index values.
*/
-/* package */ class AudioDeviceInfoState {
+/* package */ class CarAudioDeviceInfo {
+
private final ContentResolver mContentResolver;
private final AudioDeviceInfo mAudioDeviceInfo;
private final int mBusNumber;
@@ -41,23 +47,22 @@ import com.android.internal.util.Preconditions;
private int mCurrentGainIndex;
- AudioDeviceInfoState(Context context, AudioDeviceInfo audioDeviceInfo) {
+ CarAudioDeviceInfo(Context context, AudioDeviceInfo audioDeviceInfo) {
mContentResolver = context.getContentResolver();
mAudioDeviceInfo = audioDeviceInfo;
mBusNumber = parseDeviceAddress(mAudioDeviceInfo.getAddress());
mSampleRate = getMaxSampleRate(audioDeviceInfo);
mChannelCount = getMaxChannels(audioDeviceInfo);
final AudioGain audioGain = Preconditions.checkNotNull(
- CarAudioService.getAudioGain(audioDeviceInfo.getPort()),
- "No audio gain on device port " + audioDeviceInfo);
- mMaxGainIndex = CarAudioService.gainToIndex(audioGain, audioGain.maxValue());
- mMinGainIndex = CarAudioService.gainToIndex(audioGain, audioGain.minValue());
+ getAudioGain(), "No audio gain on device port " + audioDeviceInfo);
+ mMaxGainIndex = gainToIndex(audioGain, audioGain.maxValue());
+ mMinGainIndex = gainToIndex(audioGain, audioGain.minValue());
// Get the current gain index from persistent storage and fallback to default.
mCurrentGainIndex = Settings.Global.getInt(mContentResolver,
CarAudioManager.getVolumeSettingsKeyForBus(mBusNumber), -1);
if (mCurrentGainIndex < 0) {
- mCurrentGainIndex = CarAudioService.gainToIndex(audioGain, audioGain.defaultValue());
+ mCurrentGainIndex = gainToIndex(audioGain, audioGain.defaultValue());
}
}
@@ -65,6 +70,10 @@ import com.android.internal.util.Preconditions;
return mAudioDeviceInfo;
}
+ AudioDevicePort getAudioDevicePort() {
+ return mAudioDeviceInfo.getPort();
+ }
+
int getBusNumber() {
return mBusNumber;
}
@@ -93,9 +102,26 @@ import com.android.internal.util.Preconditions;
Preconditions.checkArgument(
gainIndex >= mMinGainIndex && gainIndex <= mMaxGainIndex,
"Invalid gain index: " + gainIndex);
- Settings.Global.putInt(mContentResolver,
- CarAudioManager.getVolumeSettingsKeyForBus(mBusNumber), gainIndex);
- mCurrentGainIndex = gainIndex;
+
+ // Calls to AudioManager.setAudioPortConfig
+ AudioGainConfig audioGainConfig = null;
+ AudioGain audioGain = getAudioGain();
+ if (audioGain != null) {
+ audioGainConfig = getPortGainForIndex(gainIndex);
+ }
+ if (audioGainConfig != null) {
+ int r = AudioManager.setAudioPortGain(getAudioDevicePort(), audioGainConfig);
+ if (r == 0) {
+ // Updates the setting and internal state only if setAudioPortGain succeeds
+ Settings.Global.putInt(mContentResolver,
+ CarAudioManager.getVolumeSettingsKeyForBus(mBusNumber), gainIndex);
+ mCurrentGainIndex = gainIndex;
+ } else {
+ Log.e(CarLog.TAG_AUDIO, "Failed to setAudioPortGain: " + r);
+ }
+ } else {
+ Log.e(CarLog.TAG_AUDIO, "Failed to construct AudioGainConfig");
+ }
}
/**
@@ -149,6 +175,75 @@ import com.android.internal.util.Preconditions;
return channels;
}
+ /**
+ * @return {@link AudioGain} with {@link AudioGain#MODE_JOINT} on a given {@link AudioPort}
+ */
+ AudioGain getAudioGain() {
+ final AudioDevicePort audioPort = getAudioDevicePort();
+ if (audioPort != null && audioPort.gains().length > 0) {
+ for (AudioGain audioGain : audioPort.gains()) {
+ if ((audioGain.mode() & AudioGain.MODE_JOINT) != 0) {
+ return checkAudioGainConfiguration(audioGain);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Constraints applied to gain configuration, see also audio_policy_configuration.xml
+ */
+ private AudioGain checkAudioGainConfiguration(AudioGain audioGain) {
+ Preconditions.checkArgument(audioGain.maxValue() >= audioGain.minValue());
+ Preconditions.checkArgument((audioGain.defaultValue() >= audioGain.minValue())
+ && (audioGain.defaultValue() <= audioGain.maxValue()));
+ Preconditions.checkArgument(
+ ((audioGain.maxValue() - audioGain.minValue()) % audioGain.stepValue()) == 0);
+ Preconditions.checkArgument(
+ ((audioGain.defaultValue() - audioGain.minValue()) % audioGain.stepValue()) == 0);
+ return audioGain;
+ }
+
+ /**
+ * @param audioGain {@link AudioGain} on a {@link AudioPort}
+ * @param gain Gain value in millibel
+ * @return index value depends on max / min / step of a given {@link AudioGain}
+ */
+ private int gainToIndex(AudioGain audioGain, int gain) {
+ gain = checkGainBound(audioGain, gain);
+ return (gain - audioGain.minValue()) / audioGain.stepValue();
+ }
+
+ /**
+ * @param audioGain {@link AudioGain} on a {@link AudioPort}
+ * @param index index value depends on max / min / step of a given {@link AudioGain}
+ * @return gain value in millibel
+ */
+ private int indexToGain(AudioGain audioGain, int index) {
+ final int gain = index * audioGain.stepValue() + audioGain.minValue();
+ return checkGainBound(audioGain, gain);
+ }
+
+ private int checkGainBound(AudioGain audioGain, int gain) {
+ if (gain < audioGain.minValue() || gain > audioGain.maxValue()) {
+ throw new RuntimeException("Gain value out of bound: " + gain);
+ }
+ return gain;
+ }
+
+ @Nullable
+ AudioGainConfig getPortGainForIndex(int index) {
+ AudioGainConfig audioGainConfig = null;
+ AudioGain audioGain = getAudioGain();
+ if (audioGain != null) {
+ int gainValue = indexToGain(audioGain, index);
+ // size of gain values is 1 in MODE_JOINT
+ audioGainConfig = audioGain.buildConfig(AudioGain.MODE_JOINT,
+ audioGain.channelMask(), new int[] { gainValue }, 0);
+ }
+ return audioGainConfig;
+ }
+
@Override
public String toString() {
return "device: " + mAudioDeviceInfo.getAddress()
diff --git a/service/src/com/android/car/CarAudioService.java b/service/src/com/android/car/CarAudioService.java
index b1beed920b..20b9f3f3ce 100644
--- a/service/src/com/android/car/CarAudioService.java
+++ b/service/src/com/android/car/CarAudioService.java
@@ -19,7 +19,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.car.Car;
import android.car.media.CarAudioPatchHandle;
-import android.car.media.CarVolumeGroup;
import android.car.media.ICarAudio;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -30,12 +29,10 @@ import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioDevicePort;
import android.media.AudioFormat;
-import android.media.AudioGain;
import android.media.AudioGainConfig;
import android.media.AudioManager;
import android.media.AudioPatch;
import android.media.AudioPlaybackConfiguration;
-import android.media.AudioPort;
import android.media.AudioPortConfig;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioMixingRule;
@@ -51,8 +48,11 @@ import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.NoSuchElementException;
+import java.util.Set;
import java.util.stream.Collectors;
public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
@@ -103,8 +103,8 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
private final TelephonyManager mTelephonyManager;
private final AudioManager mAudioManager;
private final boolean mUseDynamicRouting;
- private final SparseIntArray mUsageToBus = new SparseIntArray();
- private final SparseArray<AudioDeviceInfoState> mAudioDeviceInfoStates = new SparseArray<>();
+ private final SparseIntArray mContextToBus = new SparseIntArray();
+ private final SparseArray<CarAudioDeviceInfo> mCarAudioDeviceInfos = new SparseArray<>();
private final AudioPolicy.AudioPolicyVolumeCallback mAudioPolicyVolumeCallback =
new AudioPolicy.AudioPolicyVolumeCallback() {
@@ -114,17 +114,18 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
Log.v(CarLog.TAG_AUDIO,
"onVolumeAdjustment: " + AudioManager.adjustToString(adjustment)
+ " suggested usage: " + AudioAttributes.usageToString(usage));
- final int currentVolume = getUsageVolume(usage);
+ final int groupId = getVolumeGroupIdForUsage(usage);
+ final int currentVolume = getGroupVolume(groupId);
final int flags = AudioManager.FLAG_FROM_KEY;
switch (adjustment) {
case AudioManager.ADJUST_LOWER:
- if (currentVolume > getUsageMinVolume(usage)) {
- setUsageVolume(usage, currentVolume - 1, flags);
+ if (currentVolume > getGroupMinVolume(groupId)) {
+ setGroupVolume(groupId, currentVolume - 1, flags);
}
break;
case AudioManager.ADJUST_RAISE:
- if (currentVolume < getUsageMaxVolume(usage)) {
- setUsageVolume(usage, currentVolume + 1, flags);
+ if (currentVolume < getGroupMaxVolume(groupId)) {
+ setGroupVolume(groupId, currentVolume + 1, flags);
}
break;
case AudioManager.ADJUST_MUTE:
@@ -144,7 +145,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
};
private AudioPolicy mAudioPolicy;
- private CarVolumeGroup[] mCarVolumeGroups = new CarVolumeGroup[0];
+ private CarVolumeGroup[] mCarVolumeGroups;
public CarAudioService(Context context) {
mContext = context;
@@ -154,13 +155,19 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
mUseDynamicRouting = res.getBoolean(R.bool.audioUseDynamicRouting);
}
+ /**
+ * Dynamic routing and volume groups are set only if
+ * {@link #mUseDynamicRouting} is {@code true}. Otherwise, this service runs in legacy mode.
+ */
@Override
public void init() {
if (!mUseDynamicRouting) {
Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not configured, run in legacy mode");
return;
}
+
setupDynamicRouting();
+ setupVolumeGroups();
}
@Override
@@ -176,66 +183,61 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
writer.println("*CarAudioService*");
writer.println("mUseDynamicRouting: " + mUseDynamicRouting);
if (mUseDynamicRouting) {
- int size = mAudioDeviceInfoStates.size();
- for (int i = 0; i < size; i++) {
- writer.println("\tBus number: " + mAudioDeviceInfoStates.keyAt(i));
- AudioDeviceInfoState state = mAudioDeviceInfoStates.valueAt(i);
- writer.printf("\tGain configuration: %s\n", state.toString());
+ for (CarVolumeGroup group : mCarVolumeGroups) {
+ writer.println("\tVolume group: " + group);
}
}
}
/**
- * @see {@link android.car.media.CarAudioManager#setUsageVolume(int, int, int)}
+ * @see {@link android.car.media.CarAudioManager#setGroupVolume(int, int, int)}
*/
@Override
- public void setUsageVolume(
- @AudioAttributes.AttributeUsage int usage, int index, int flags) {
+ public void setGroupVolume(int groupId, int index, int flags) {
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
- AudioPort audioPort = getAudioPort(usage);
- AudioGainConfig audioGainConfig = null;
- AudioGain audioGain = getAudioGain(audioPort);
- if (audioGain != null) {
- audioGainConfig = getPortGainForIndex(audioPort, index);
- }
- if (audioGainConfig != null) {
- int r = AudioManager.setAudioPortGain(audioPort, audioGainConfig);
- if (r == 0) {
- AudioDeviceInfoState state = mAudioDeviceInfoStates.get(mUsageToBus.get(usage));
- state.setCurrentGainIndex(gainToIndex(audioGain, audioGainConfig.values()[0]));
- }
- }
+
+ CarVolumeGroup group = getCarVolumeGroup(groupId);
+ group.setCurrentGainIndex(index);
}
/**
- * @see {@link android.car.media.CarAudioManager#getUsageMaxVolume(int)}
+ * @see {@link android.car.media.CarAudioManager#getGroupMaxVolume(int)}
*/
@Override
- public int getUsageMaxVolume(@AudioAttributes.AttributeUsage int usage) {
+ public int getGroupMaxVolume(int groupId) {
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
- final AudioDeviceInfoState state = mAudioDeviceInfoStates.get(mUsageToBus.get(usage));
- return state == null ? 0 : state.getMaxGainIndex();
+
+ CarVolumeGroup group = getCarVolumeGroup(groupId);
+ return group.getMaxGainIndex();
}
/**
- * TODO(hwwang): some audio usages may have a min volume greater than zero
- * @see {@link android.car.media.CarAudioManager#getUsageMinVolume(int)}
+ * @see {@link android.car.media.CarAudioManager#getGroupMinVolume(int)}
*/
@Override
- public int getUsageMinVolume(@AudioAttributes.AttributeUsage int usage) {
+ public int getGroupMinVolume(int groupId) {
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
- final AudioDeviceInfoState state = mAudioDeviceInfoStates.get(mUsageToBus.get(usage));
- return state == null ? 0 : state.getMinGainIndex();
+
+ CarVolumeGroup group = getCarVolumeGroup(groupId);
+ return group.getMinGainIndex();
}
/**
- * @see {@link android.car.media.CarAudioManager#getUsageVolume(int)}
+ * @see {@link android.car.media.CarAudioManager#getGroupVolume(int)}
*/
@Override
- public int getUsageVolume(@AudioAttributes.AttributeUsage int usage) {
+ public int getGroupVolume(int groupId) {
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
- final AudioDeviceInfoState state = mAudioDeviceInfoStates.get(mUsageToBus.get(usage));
- return state == null ? 0 : state.getCurrentGainIndex();
+
+ CarVolumeGroup group = getCarVolumeGroup(groupId);
+ return group.getCurrentGainIndex();
+ }
+
+ private CarVolumeGroup getCarVolumeGroup(int groupId) {
+ Preconditions.checkNotNull(mCarVolumeGroups);
+ Preconditions.checkArgument(groupId >= 0 && groupId < mCarVolumeGroups.length,
+ "groupId out of range: " + groupId);
+ return mCarVolumeGroups[groupId];
}
private void setupDynamicRouting() {
@@ -245,14 +247,80 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
}
AudioPolicy audioPolicy = getDynamicAudioPolicy(audioControl);
int r = mAudioManager.registerAudioPolicy(audioPolicy);
- if (r != 0) {
+ if (r != AudioManager.SUCCESS) {
throw new RuntimeException("registerAudioPolicy failed " + r);
}
mAudioPolicy = audioPolicy;
+ }
+ private void setupVolumeGroups() {
+ if (mCarAudioDeviceInfos.size() == 0) {
+ Log.w(CarLog.TAG_AUDIO, "No bus device is configured, skip setupVolumeGroups");
+ return;
+ }
final CarVolumeGroupsHelper helper = new CarVolumeGroupsHelper(
mContext, R.xml.car_volume_groups);
mCarVolumeGroups = helper.loadVolumeGroups();
+ for (CarVolumeGroup group : mCarVolumeGroups) {
+ for (int contextNumber : group.getContexts()) {
+ int busNumber = mContextToBus.get(contextNumber);
+ group.bind(contextNumber, busNumber, mCarAudioDeviceInfos.get(busNumber));
+ }
+ Log.v(CarLog.TAG_AUDIO, "Processed volume group: " + group);
+ }
+ // Perform validation after all volume groups are processed
+ if (!validateVolumeGroups()) {
+ throw new RuntimeException("Invalid volume groups configuration");
+ }
+ }
+
+ /**
+ * Constraints applied here:
+ *
+ * - One context should not appear in two groups
+ * - All contexts are assigned
+ * - One bus should not appear in two groups
+ * - All gain controllers in the same group have same step value
+ *
+ * Note that it is fine that there are buses not appear in any group, those buses may be
+ * reserved for other usages.
+ * Step value validation is done in {@link CarVolumeGroup#bind(int, int, CarAudioDeviceInfo)}
+ *
+ * See also the car_volume_groups.xml configuration
+ */
+ private boolean validateVolumeGroups() {
+ Set<Integer> contextSet = new HashSet<>();
+ Set<Integer> busNumberSet = new HashSet<>();
+ for (CarVolumeGroup group : mCarVolumeGroups) {
+ // One context should not appear in two groups
+ for (int context : group.getContexts()) {
+ if (contextSet.contains(context)) {
+ Log.e(CarLog.TAG_AUDIO, "Context appears in two groups: " + context);
+ return false;
+ }
+ contextSet.add(context);
+ }
+
+ // One bus should not appear in two groups
+ for (int busNumber : group.getBusNumbers()) {
+ if (busNumberSet.contains(busNumber)) {
+ Log.e(CarLog.TAG_AUDIO, "Bus appears in two groups: " + busNumber);
+ return false;
+ }
+ busNumberSet.add(busNumber);
+ }
+ }
+
+ // All contexts are assigned
+ if (contextSet.size() != CONTEXT_NUMBERS.length) {
+ Log.e(CarLog.TAG_AUDIO, "Some contexts are not assigned to group");
+ Log.e(CarLog.TAG_AUDIO, "Assigned contexts "
+ + Arrays.toString(contextSet.toArray(new Integer[contextSet.size()])));
+ Log.e(CarLog.TAG_AUDIO, "All contexts " + Arrays.toString(CONTEXT_NUMBERS));
+ return false;
+ }
+
+ return true;
}
@Nullable
@@ -272,12 +340,12 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
info.toString(), info.getId(), info.getProductName(), info.getAddress(),
info.getType()));
if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
- final AudioDeviceInfoState state = new AudioDeviceInfoState(mContext, info);
+ final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(mContext, info);
// See also the audio_policy_configuration.xml and getBusForContext in
// audio control HAL, the bus number should be no less than zero.
- if (state.getBusNumber() >= 0) {
- mAudioDeviceInfoStates.put(state.getBusNumber(), state);
- Log.i(CarLog.TAG_AUDIO, "Valid bus found " + state);
+ if (carInfo.getBusNumber() >= 0) {
+ mCarAudioDeviceInfos.put(carInfo.getBusNumber(), carInfo);
+ Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
}
}
}
@@ -286,31 +354,31 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
try {
for (int contextNumber : CONTEXT_NUMBERS) {
int busNumber = audioControl.getBusForContext(contextNumber);
- AudioDeviceInfoState state = mAudioDeviceInfoStates.get(busNumber);
- if (state == null) {
+ mContextToBus.put(contextNumber, busNumber);
+ CarAudioDeviceInfo info = mCarAudioDeviceInfos.get(busNumber);
+ if (info == null) {
Log.w(CarLog.TAG_AUDIO, "No bus configured for context: " + contextNumber);
continue;
}
AudioFormat mixFormat = new AudioFormat.Builder()
- .setSampleRate(state.getSampleRate())
+ .setSampleRate(info.getSampleRate())
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
- .setChannelMask(state.getChannelCount())
+ .setChannelMask(info.getChannelCount())
.build();
- Log.i(CarLog.TAG_AUDIO, String.format(
- "Bus number %d, sampleRate:%d, channels:0x%s",
- busNumber, state.getSampleRate(),
- Integer.toHexString(state.getChannelCount())));
int[] usages = getUsagesForContext(contextNumber);
+ Log.i(CarLog.TAG_AUDIO, "Bus number: " + busNumber
+ + " sampleRate: " + info.getSampleRate()
+ + " channels: " + info.getChannelCount()
+ + " usages: " + Arrays.toString(usages));
AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
for (int usage : usages) {
- mUsageToBus.put(usage, busNumber);
mixingRuleBuilder.addRule(
new AudioAttributes.Builder().setUsage(usage).build(),
AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
}
AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
.setFormat(mixFormat)
- .setDevice(state.getAudioDeviceInfo())
+ .setDevice(info.getAudioDeviceInfo())
.setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
.build();
builder.addMix(audioMix);
@@ -399,15 +467,15 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
// Find the named source port
- AudioDevicePort sourcePort = null;
- AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
- for (AudioDeviceInfo info: devices) {
+ AudioDeviceInfo sourcePortInfo = null;
+ AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+ for (AudioDeviceInfo info: deviceInfos) {
if (sourceName.equals(info.getProductName())) {
// This is the one for which we're looking
- sourcePort = info.getPort();
+ sourcePortInfo = info;
}
}
- if (sourcePort == null) {
+ if (sourcePortInfo == null) {
throw new IllegalArgumentException("Specified source is not available: " + sourceName);
}
@@ -421,14 +489,16 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
AudioPortConfig sinkConfig = sinkPort.activeConfig();
// Configure the source port to match the output bus with optional gain adjustment
+ final CarAudioDeviceInfo helper = new CarAudioDeviceInfo(
+ mContext, sourcePortInfo);
AudioGainConfig audioGainConfig = null;
if (gainIndex >= 0) {
- audioGainConfig = getPortGainForIndex(sourcePort, gainIndex);
+ audioGainConfig = helper.getPortGainForIndex(gainIndex);
if (audioGainConfig == null) {
Log.w(CarLog.TAG_AUDIO, "audio gain could not be applied.");
}
}
- AudioPortConfig sourceConfig = sourcePort.buildConfig(
+ AudioPortConfig sourceConfig = sourcePortInfo.getPort().buildConfig(
sinkConfig.samplingRate(),
sinkConfig.channelMask(),
sinkConfig.format(),
@@ -444,7 +514,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
throw new RuntimeException("createAudioPatch failed with code " + result);
}
if (patch[0] == null) {
- throw new RuntimeException("createAudioPatch didn't provide the expected single handle");
+ throw new RuntimeException("createAudioPatch didn't provide expected single handle");
}
return new CarAudioPatchHandle(patch[0]);
@@ -458,7 +528,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
// if the client that created a patch quits.
// Get the list of active patches
- ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
+ ArrayList<AudioPatch> patches = new ArrayList<>();
int result = AudioManager.listAudioPatches(patches);
if (result != AudioManager.SUCCESS) {
throw new RuntimeException("listAudioPatches failed with code " + result);
@@ -477,102 +547,53 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
}
// If we didn't find a match, then something went awry, but it's probably not fatal...
- Log.e(CarLog.TAG_AUDIO, "releaseAudioPatch found no match for " + carPatch.toString());
+ Log.e(CarLog.TAG_AUDIO, "releaseAudioPatch found no match for " + carPatch);
}
@Override
- public @NonNull CarVolumeGroup[] getVolumeGroups() {
- enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
+ public int getVolumeGroupCount() {
+ enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
- return mCarVolumeGroups;
+ return mCarVolumeGroups == null ? 0 : mCarVolumeGroups.length;
}
- private void enforcePermission(String permissionName) {
- if (mContext.checkCallingOrSelfPermission(permissionName)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(
- "requires permission " + permissionName);
- }
- }
+ @Override
+ public int getVolumeGroupIdForUsage(@AudioAttributes.AttributeUsage int usage) {
+ enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
- /**
- * @return {@link AudioDevicePort} that handles the given car audio usage. Multiple car
- * audio usages may share one {@link AudioDevicePort}
- */
- private @Nullable AudioDevicePort getAudioPort(@AudioAttributes.AttributeUsage int usage) {
- final int busNumber = mUsageToBus.get(usage);
- if (mAudioDeviceInfoStates.get(busNumber) != null) {
- return mAudioDeviceInfoStates.get(busNumber).getAudioDeviceInfo().getPort();
+ if (mCarVolumeGroups == null) {
+ return -1;
}
- return null;
- }
- /**
- * @return {@link AudioGain} with {@link AudioGain#MODE_JOINT} on a given {@link AudioPort}
- */
- static @Nullable AudioGain getAudioGain(AudioPort audioPort) {
- if (audioPort != null && audioPort.gains().length > 0) {
- for (AudioGain audioGain : audioPort.gains()) {
- if ((audioGain.mode() & AudioGain.MODE_JOINT) != 0) {
- return checkAudioGainConfiguration(audioGain);
+ for (int i = 0; i < mCarVolumeGroups.length; i++) {
+ int[] contexts = mCarVolumeGroups[i].getContexts();
+ for (int context : contexts) {
+ if (USAGE_TO_CONTEXT.get(usage) == context) {
+ return i;
}
}
}
- return null;
- }
-
- /**
- * Constraints applied to gain configuration, see also audio_policy_configuration.xml
- */
- private static AudioGain checkAudioGainConfiguration(AudioGain audioGain) {
- Preconditions.checkArgument(audioGain.maxValue() >= audioGain.minValue());
- Preconditions.checkArgument((audioGain.defaultValue() >= audioGain.minValue())
- && (audioGain.defaultValue() <= audioGain.maxValue()));
- Preconditions.checkArgument(
- ((audioGain.maxValue() - audioGain.minValue()) % audioGain.stepValue()) == 0);
- Preconditions.checkArgument(
- ((audioGain.defaultValue() - audioGain.minValue()) % audioGain.stepValue()) == 0);
- return audioGain;
+ return -1;
}
- /**
- * @param audioGain {@link AudioGain} on a {@link AudioPort}
- * @param gain Gain value in millibel
- * @return index value depends on max / min / step of a given {@link AudioGain}
- */
- static int gainToIndex(AudioGain audioGain, int gain) {
- gain = checkGainBound(audioGain, gain);
- return (gain - audioGain.minValue()) / audioGain.stepValue();
+ private void enforcePermission(String permissionName) {
+ if (mContext.checkCallingOrSelfPermission(permissionName)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "requires permission " + permissionName);
+ }
}
/**
- * @param audioGain {@link AudioGain} on a {@link AudioPort}
- * @param index index value depends on max / min / step of a given {@link AudioGain}
- * @return gain value in millibel
+ * @return {@link AudioDevicePort} that handles the given car audio usage.
+ * Multiple usages may share one {@link AudioDevicePort}
*/
- static int indexToGain(AudioGain audioGain, int index) {
- final int gain = index * audioGain.stepValue() + audioGain.minValue();
- return checkGainBound(audioGain, gain);
- }
-
- private static int checkGainBound(AudioGain audioGain, int gain) {
- if (gain < audioGain.minValue() || gain > audioGain.maxValue()) {
- throw new RuntimeException("Gain value out of bound: " + gain);
- }
- return gain;
- }
-
- @Nullable
- private AudioGainConfig getPortGainForIndex(AudioPort port, int index) {
- AudioGainConfig audioGainConfig = null;
- AudioGain audioGain = getAudioGain(port);
- if (audioGain != null) {
- int gainValue = indexToGain(audioGain, index);
- // size of gain values is 1 in MODE_JOINT
- audioGainConfig = audioGain.buildConfig(AudioGain.MODE_JOINT,
- audioGain.channelMask(), new int[] { gainValue }, 0);
- }
- return audioGainConfig;
+ private @Nullable AudioDevicePort getAudioPort(@AudioAttributes.AttributeUsage int usage) {
+ final int groupId = getVolumeGroupIdForUsage(usage);
+ final CarVolumeGroup group = Preconditions.checkNotNull(mCarVolumeGroups[groupId],
+ "Can not find CarVolumeGroup by usage: "
+ + AudioAttributes.usageToString(usage));
+ return group.getAudioDevicePortForContext(USAGE_TO_CONTEXT.get(usage));
}
/**
@@ -588,7 +609,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
List<AudioPlaybackConfiguration> playbacks = mAudioManager
.getActivePlaybackConfigurations()
.stream()
- .filter(p -> p.isActive())
+ .filter(AudioPlaybackConfiguration::isActive)
.collect(Collectors.toList());
if (!playbacks.isEmpty()) {
// Get audio usage from active playbacks if there is any, last one if multiple
diff --git a/service/src/com/android/car/CarVolumeGroup.java b/service/src/com/android/car/CarVolumeGroup.java
new file mode 100644
index 0000000000..a0aeff1bd5
--- /dev/null
+++ b/service/src/com/android/car/CarVolumeGroup.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2018 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.car.media.CarAudioManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.AudioDevicePort;
+import android.provider.Settings;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+
+/**
+ * A class encapsulates a volume group in car.
+ *
+ * Volume in a car is controlled by group. A group holds one or more car audio contexts.
+ * Call {@link CarAudioManager#getVolumeGroupCount()} to get the count of {@link CarVolumeGroup}
+ * supported in a car.
+ */
+/* package */ final class CarVolumeGroup {
+
+ private final ContentResolver mContentResolver;
+ private final int mId;
+ private final int[] mContexts;
+ private final SparseIntArray mContextToBus = new SparseIntArray();
+ private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfos = new SparseArray<>();
+
+ private int mGroupMaxGainIndex = Integer.MIN_VALUE;
+ private int mGroupMinGainIndex = Integer.MAX_VALUE;
+ private int mCurrentGainIndex;
+
+ CarVolumeGroup(Context context, int id, @NonNull int[] contexts) {
+ mContentResolver = context.getContentResolver();
+ mId = id;
+ mContexts = contexts;
+
+ mCurrentGainIndex = Settings.Global.getInt(mContentResolver,
+ CarAudioManager.getVolumeSettingsKeyForGroup(mId), -1);;
+ }
+
+ int getId() {
+ return mId;
+ }
+
+ int[] getContexts() {
+ return mContexts;
+ }
+
+ int[] getBusNumbers() {
+ final int[] busNumbers = new int[mBusToCarAudioDeviceInfos.size()];
+ for (int i = 0; i < busNumbers.length; i++) {
+ busNumbers[i] = mBusToCarAudioDeviceInfos.keyAt(i);
+ }
+ return busNumbers;
+ }
+
+ /**
+ * Binds the context number to physical bus number and audio device port information.
+ *
+ * @param contextNumber Context number as defined in audio control HAL
+ * @param busNumber Physical bus number for the audio device port
+ * @param info {@link CarAudioDeviceInfo} instance relates to the physical bus
+ */
+ void bind(int contextNumber, int busNumber, CarAudioDeviceInfo info) {
+ if (mBusToCarAudioDeviceInfos.size() > 0) {
+ final int stepValue =
+ mBusToCarAudioDeviceInfos.valueAt(0).getAudioGain().stepValue();
+ Preconditions.checkArgument(
+ info.getAudioGain().stepValue() == stepValue,
+ "Gain controls within one group should have same step value");
+ }
+ mContextToBus.put(contextNumber, busNumber);
+ mBusToCarAudioDeviceInfos.put(busNumber, info);
+ if (info.getMaxGainIndex() > mGroupMaxGainIndex) {
+ mGroupMaxGainIndex = info.getMaxGainIndex();
+ }
+ if (info.getMinGainIndex() < mGroupMinGainIndex) {
+ mGroupMinGainIndex = info.getMinGainIndex();
+ }
+ if (mCurrentGainIndex < 0) {
+ // TODO: check the boundary of each gain control.
+ // It's possible that each bus has different current gain index. If it's due to minimum
+ // allowed, the current should be set to Math.min(currents), if it's due to maximum
+ // allowed, the current should be set to Math.max(currents). Otherwise, the current
+ // should be identical among the buses.
+ mCurrentGainIndex = info.getCurrentGainIndex();
+ }
+ }
+
+ int getMaxGainIndex() {
+ return mGroupMaxGainIndex;
+ }
+
+ int getMinGainIndex() {
+ return mGroupMinGainIndex;
+ }
+
+ int getCurrentGainIndex() {
+ return mCurrentGainIndex;
+ }
+
+ void setCurrentGainIndex(int gainIndex) {
+ Preconditions.checkArgument(
+ gainIndex >= mGroupMinGainIndex && gainIndex <= mGroupMaxGainIndex,
+ "Invalid gain index: " + gainIndex);
+
+ for (int i = 0; i < mBusToCarAudioDeviceInfos.size(); i++) {
+ CarAudioDeviceInfo info = mBusToCarAudioDeviceInfos.valueAt(i);
+ // It's possible that each CarAudioDeviceInfo has different boundary than the group.
+ if (gainIndex < info.getMinGainIndex()) {
+ info.setCurrentGainIndex(info.getMinGainIndex());
+ } else if (gainIndex > info.getMaxGainIndex()) {
+ info.setCurrentGainIndex(info.getMaxGainIndex());
+ } else {
+ info.setCurrentGainIndex(gainIndex);
+ }
+ }
+
+ mCurrentGainIndex = gainIndex;
+ Settings.Global.putInt(mContentResolver,
+ CarAudioManager.getVolumeSettingsKeyForGroup(mId), gainIndex);
+ }
+
+ @Nullable
+ AudioDevicePort getAudioDevicePortForContext(int contextNumber) {
+ final int busNumber = mContextToBus.get(contextNumber,
+ android.hardware.automotive.audiocontrol.V1_0.ContextNumber.INVALID);
+ if (busNumber == android.hardware.automotive.audiocontrol.V1_0.ContextNumber.INVALID
+ || mBusToCarAudioDeviceInfos.get(busNumber) == null) {
+ return null;
+ }
+ return mBusToCarAudioDeviceInfos.get(busNumber).getAudioDevicePort();
+ }
+
+ @Override
+ public String toString() {
+ return "CarVolumeGroup id: " + mId
+ + " currentGainIndex: " + mCurrentGainIndex
+ + " contexts: " + Arrays.toString(mContexts)
+ + " buses: " + Arrays.toString(getBusNumbers());
+ }
+}
diff --git a/service/src/com/android/car/CarVolumeGroupsHelper.java b/service/src/com/android/car/CarVolumeGroupsHelper.java
index 86e0a8944a..af2c6ddb93 100644
--- a/service/src/com/android/car/CarVolumeGroupsHelper.java
+++ b/service/src/com/android/car/CarVolumeGroupsHelper.java
@@ -16,9 +16,7 @@
package com.android.car;
import android.annotation.XmlRes;
-import android.car.media.CarVolumeGroup;
import android.content.Context;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.util.AttributeSet;
@@ -37,17 +35,17 @@ import java.util.List;
private static final String TAG_GROUP = "group";
private static final String TAG_CONTEXT = "context";
- private final Resources mResources;
+ private final Context mContext;
private final @XmlRes int mXmlConfiguration;
CarVolumeGroupsHelper(Context context, @XmlRes int xmlConfiguration) {
- mResources = context.getResources();
+ mContext = context;
mXmlConfiguration = xmlConfiguration;
}
CarVolumeGroup[] loadVolumeGroups() {
List<CarVolumeGroup> carVolumeGroups = new ArrayList<>();
- try (XmlResourceParser parser = mResources.getXml(mXmlConfiguration)) {
+ try (XmlResourceParser parser = mContext.getResources().getXml(mXmlConfiguration)) {
AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
// Traverse to the first start tag
@@ -59,13 +57,15 @@ import java.util.List;
throw new RuntimeException("Meta-data does not start with volumeGroups tag");
}
int outerDepth = parser.getDepth();
+ int id = 0;
while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
&& (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlResourceParser.END_TAG) {
continue;
}
if (TAG_GROUP.equals(parser.getName())) {
- carVolumeGroups.add(parseVolumeGroup(attrs, parser));
+ carVolumeGroups.add(parseVolumeGroup(id, attrs, parser));
+ id++;
}
}
} catch (Exception e) {
@@ -74,12 +74,9 @@ import java.util.List;
return carVolumeGroups.toArray(new CarVolumeGroup[carVolumeGroups.size()]);
}
- private CarVolumeGroup parseVolumeGroup(AttributeSet attrs, XmlResourceParser parser)
+ private CarVolumeGroup parseVolumeGroup(int id, AttributeSet attrs, XmlResourceParser parser)
throws XmlPullParserException, IOException {
int type;
- TypedArray a = mResources.obtainAttributes(attrs, R.styleable.volumeGroups_group);
- String title = a.getString(R.styleable.volumeGroups_group_name);
- a.recycle();
List<Integer> contexts = new ArrayList<>();
int innerDepth = parser.getDepth();
@@ -89,14 +86,14 @@ import java.util.List;
continue;
}
if (TAG_CONTEXT.equals(parser.getName())) {
- TypedArray c = mResources.obtainAttributes(
+ TypedArray c = mContext.getResources().obtainAttributes(
attrs, R.styleable.volumeGroups_context);
contexts.add(c.getInt(R.styleable.volumeGroups_context_context, -1));
c.recycle();
}
}
- return new CarVolumeGroup(title,
+ return new CarVolumeGroup(mContext, id,
contexts.stream().mapToInt(i -> i).filter(i -> i >= 0).toArray());
}
}