blob: a87b4b1a9d5076c9beeef8bbf1946233a125ac9f [file] [log] [blame]
/*
* 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.audio;
import android.car.media.CarAudioManager;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.util.Log;
import android.view.DisplayAddress;
import com.android.car.CarLog;
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.Set;
/**
* A class encapsulates an audio zone in car.
*
* An audio zone can contain multiple {@link CarVolumeGroup}s, and each zone has its own
* {@link CarAudioFocus} instance. Additionally, there may be dedicated hardware volume keys
* attached to each zone.
*
* See also the unified car_audio_configuration.xml
*/
/* package */ class CarAudioZone {
private final int mId;
private final String mName;
private final List<CarVolumeGroup> mVolumeGroups;
private final List<DisplayAddress.Physical> mPhysicalDisplayAddresses;
private List<AudioDeviceAttributes> mInputAudioDevice;
CarAudioZone(int id, String name) {
mId = id;
mName = name;
mVolumeGroups = new ArrayList<>();
mPhysicalDisplayAddresses = new ArrayList<>();
mInputAudioDevice = new ArrayList<>();
}
int getId() {
return mId;
}
String getName() {
return mName;
}
boolean isPrimaryZone() {
return mId == CarAudioManager.PRIMARY_AUDIO_ZONE;
}
void addVolumeGroup(CarVolumeGroup volumeGroup) {
mVolumeGroups.add(volumeGroup);
}
CarVolumeGroup getVolumeGroup(int groupId) {
Preconditions.checkArgumentInRange(groupId, 0, mVolumeGroups.size() - 1,
"groupId(" + groupId + ") is out of range");
return mVolumeGroups.get(groupId);
}
/**
* @return Snapshot of available {@link AudioDeviceInfo}s in List.
*/
List<AudioDeviceInfo> getAudioDeviceInfos() {
final List<AudioDeviceInfo> devices = new ArrayList<>();
for (CarVolumeGroup group : mVolumeGroups) {
for (String address : group.getAddresses()) {
devices.add(group.getCarAudioDeviceInfoForAddress(address).getAudioDeviceInfo());
}
}
return devices;
}
int getVolumeGroupCount() {
return mVolumeGroups.size();
}
/**
* Associates a new display physical port with this audio zone. This can be used to
* identify what zone an activity should produce sound in when launching on a particular display
* @param physicalDisplayAddress port to associate with this zone
*/
void addPhysicalDisplayAddress(DisplayAddress.Physical physicalDisplayAddress) {
mPhysicalDisplayAddresses.add(physicalDisplayAddress);
}
/**
* Gets list of ports for displays associated with this audio zone
* @return list of Physical ports for displays associated with this audio zone
*/
List<DisplayAddress.Physical> getPhysicalDisplayAddresses() {
return mPhysicalDisplayAddresses;
}
/**
* @return Snapshot of available {@link CarVolumeGroup}s in array.
*/
CarVolumeGroup[] getVolumeGroups() {
return mVolumeGroups.toArray(new CarVolumeGroup[0]);
}
/**
* Constraints applied here:
*
* - One context should not appear in two groups
* - All contexts are assigned
* - One device 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 devices which do not appear in any group. Those devices
* may be reserved for other purposes.
* Step value validation is done in {@link CarVolumeGroup#bind(int, CarAudioDeviceInfo)}
*/
boolean validateVolumeGroups() {
Set<Integer> contextSet = new HashSet<>();
Set<String> addresses = new HashSet<>();
for (CarVolumeGroup group : mVolumeGroups) {
// One context should not appear in two groups
for (int context : group.getContexts()) {
if (!contextSet.add(context)) {
Log.e(CarLog.TAG_AUDIO, "Context appears in two groups: " + context);
return false;
}
}
// One address should not appear in two groups
for (String address : group.getAddresses()) {
if (!addresses.add(address)) {
Log.e(CarLog.TAG_AUDIO, "Address appears in two groups: " + address);
return false;
}
}
}
// All contexts are assigned
if (contextSet.size() != CarAudioContext.CONTEXTS.length) {
Log.e(CarLog.TAG_AUDIO, "Some contexts are not assigned to group");
Log.e(CarLog.TAG_AUDIO, "Assigned contexts " + contextSet);
Log.e(CarLog.TAG_AUDIO,
"All contexts " + Arrays.toString(CarAudioContext.CONTEXTS));
return false;
}
return true;
}
void synchronizeCurrentGainIndex() {
for (CarVolumeGroup group : mVolumeGroups) {
group.setCurrentGainIndex(group.getCurrentGainIndex());
}
}
void dump(String indent, PrintWriter writer) {
String internalIndent = indent + "\t";
writer.printf("%sCarAudioZone(%s:%d) isPrimary? %b\n", indent, mName, mId, isPrimaryZone());
for (DisplayAddress.Physical physical: mPhysicalDisplayAddresses) {
long port = (long) physical.getPort();
writer.printf("%sDisplayAddress.Physical(%d)\n", internalIndent, port);
}
writer.println();
for (CarVolumeGroup group : mVolumeGroups) {
group.dump(internalIndent, writer);
}
writer.printf("%sInput Audio Device Addresses\n", internalIndent);
String devicesIndent = internalIndent + "\t";
for (AudioDeviceAttributes audioDevice : mInputAudioDevice) {
writer.printf("%sDevice Address(%s)\n", devicesIndent,
audioDevice.getAddress());
}
writer.println();
}
String getAddressForContext(int audioContext) {
CarAudioContext.preconditionCheckAudioContext(audioContext);
String deviceAddress = null;
for (CarVolumeGroup volumeGroup : getVolumeGroups()) {
deviceAddress = volumeGroup.getAddressForContext(audioContext);
if (deviceAddress != null) {
return deviceAddress;
}
}
// This should not happen unless something went wrong.
// Device address are unique per zone and all contexts are assigned in a zone.
throw new IllegalStateException("Could not find output device in zone " + mId
+ " for audio context " + audioContext);
}
/**
* Update the volume groups for the new user
* @param userId user id to update to
*/
public void updateVolumeGroupsForUser(int userId) {
for (CarVolumeGroup group : mVolumeGroups) {
group.loadVolumesForUser(userId);
}
}
void addInputAudioDevice(AudioDeviceAttributes device) {
mInputAudioDevice.add(device);
}
List<AudioDeviceAttributes> getInputAudioDevices() {
return mInputAudioDevice;
}
}