1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.car.audio; 17 18 import android.car.media.CarAudioManager; 19 import android.media.AudioDeviceAttributes; 20 import android.media.AudioDeviceInfo; 21 import android.util.Log; 22 23 import com.android.car.CarLog; 24 import com.android.internal.util.Preconditions; 25 26 import java.io.PrintWriter; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.HashSet; 30 import java.util.List; 31 import java.util.Set; 32 33 /** 34 * A class encapsulates an audio zone in car. 35 * 36 * An audio zone can contain multiple {@link CarVolumeGroup}s, and each zone has its own 37 * {@link CarAudioFocus} instance. Additionally, there may be dedicated hardware volume keys 38 * attached to each zone. 39 * 40 * See also the unified car_audio_configuration.xml 41 */ 42 /* package */ class CarAudioZone { 43 44 private final int mId; 45 private final String mName; 46 private final List<CarVolumeGroup> mVolumeGroups; 47 private List<AudioDeviceAttributes> mInputAudioDevice; 48 CarAudioZone(int id, String name)49 CarAudioZone(int id, String name) { 50 mId = id; 51 mName = name; 52 mVolumeGroups = new ArrayList<>(); 53 mInputAudioDevice = new ArrayList<>(); 54 } 55 getId()56 int getId() { 57 return mId; 58 } 59 getName()60 String getName() { 61 return mName; 62 } 63 isPrimaryZone()64 boolean isPrimaryZone() { 65 return mId == CarAudioManager.PRIMARY_AUDIO_ZONE; 66 } 67 addVolumeGroup(CarVolumeGroup volumeGroup)68 void addVolumeGroup(CarVolumeGroup volumeGroup) { 69 mVolumeGroups.add(volumeGroup); 70 } 71 getVolumeGroup(int groupId)72 CarVolumeGroup getVolumeGroup(int groupId) { 73 Preconditions.checkArgumentInRange(groupId, 0, mVolumeGroups.size() - 1, 74 "groupId(" + groupId + ") is out of range"); 75 return mVolumeGroups.get(groupId); 76 } 77 78 /** 79 * @return Snapshot of available {@link AudioDeviceInfo}s in List. 80 */ getAudioDeviceInfos()81 List<AudioDeviceInfo> getAudioDeviceInfos() { 82 final List<AudioDeviceInfo> devices = new ArrayList<>(); 83 for (CarVolumeGroup group : mVolumeGroups) { 84 for (String address : group.getAddresses()) { 85 devices.add(group.getCarAudioDeviceInfoForAddress(address).getAudioDeviceInfo()); 86 } 87 } 88 return devices; 89 } 90 getVolumeGroupCount()91 int getVolumeGroupCount() { 92 return mVolumeGroups.size(); 93 } 94 95 /** 96 * @return Snapshot of available {@link CarVolumeGroup}s in array. 97 */ getVolumeGroups()98 CarVolumeGroup[] getVolumeGroups() { 99 return mVolumeGroups.toArray(new CarVolumeGroup[0]); 100 } 101 102 /** 103 * Constraints applied here: 104 * 105 * - One context should not appear in two groups 106 * - All contexts are assigned 107 * - One device should not appear in two groups 108 * - All gain controllers in the same group have same step value 109 * 110 * Note that it is fine that there are devices which do not appear in any group. Those devices 111 * may be reserved for other purposes. 112 * Step value validation is done in {@link CarVolumeGroup#bind(int, CarAudioDeviceInfo)} 113 */ validateVolumeGroups()114 boolean validateVolumeGroups() { 115 Set<Integer> contextSet = new HashSet<>(); 116 Set<String> addresses = new HashSet<>(); 117 for (CarVolumeGroup group : mVolumeGroups) { 118 // One context should not appear in two groups 119 for (int context : group.getContexts()) { 120 if (!contextSet.add(context)) { 121 Log.e(CarLog.TAG_AUDIO, "Context appears in two groups: " + context); 122 return false; 123 } 124 } 125 126 // One address should not appear in two groups 127 for (String address : group.getAddresses()) { 128 if (!addresses.add(address)) { 129 Log.e(CarLog.TAG_AUDIO, "Address appears in two groups: " + address); 130 return false; 131 } 132 } 133 } 134 135 // All contexts are assigned 136 if (contextSet.size() != CarAudioContext.CONTEXTS.length) { 137 Log.e(CarLog.TAG_AUDIO, "Some contexts are not assigned to group"); 138 Log.e(CarLog.TAG_AUDIO, "Assigned contexts " + contextSet); 139 Log.e(CarLog.TAG_AUDIO, 140 "All contexts " + Arrays.toString(CarAudioContext.CONTEXTS)); 141 return false; 142 } 143 144 return true; 145 } 146 synchronizeCurrentGainIndex()147 void synchronizeCurrentGainIndex() { 148 for (CarVolumeGroup group : mVolumeGroups) { 149 group.setCurrentGainIndex(group.getCurrentGainIndex()); 150 } 151 } 152 dump(String indent, PrintWriter writer)153 void dump(String indent, PrintWriter writer) { 154 String internalIndent = indent + "\t"; 155 writer.printf("%sCarAudioZone(%s:%d) isPrimary? %b\n", indent, mName, mId, isPrimaryZone()); 156 157 for (CarVolumeGroup group : mVolumeGroups) { 158 group.dump(internalIndent, writer); 159 } 160 161 writer.printf("%sInput Audio Device Addresses\n", internalIndent); 162 String devicesIndent = internalIndent + "\t"; 163 for (AudioDeviceAttributes audioDevice : mInputAudioDevice) { 164 writer.printf("%sDevice Address(%s)\n", devicesIndent, 165 audioDevice.getAddress()); 166 } 167 writer.println(); 168 } 169 getAddressForContext(int audioContext)170 String getAddressForContext(int audioContext) { 171 CarAudioContext.preconditionCheckAudioContext(audioContext); 172 String deviceAddress = null; 173 for (CarVolumeGroup volumeGroup : getVolumeGroups()) { 174 deviceAddress = volumeGroup.getAddressForContext(audioContext); 175 if (deviceAddress != null) { 176 return deviceAddress; 177 } 178 } 179 // This should not happen unless something went wrong. 180 // Device address are unique per zone and all contexts are assigned in a zone. 181 throw new IllegalStateException("Could not find output device in zone " + mId 182 + " for audio context " + audioContext); 183 } 184 185 /** 186 * Update the volume groups for the new user 187 * @param userId user id to update to 188 */ updateVolumeGroupsForUser(int userId)189 public void updateVolumeGroupsForUser(int userId) { 190 for (CarVolumeGroup group : mVolumeGroups) { 191 group.loadVolumesForUser(userId); 192 } 193 } 194 addInputAudioDevice(AudioDeviceAttributes device)195 void addInputAudioDevice(AudioDeviceAttributes device) { 196 mInputAudioDevice.add(device); 197 } 198 getInputAudioDevices()199 List<AudioDeviceAttributes> getInputAudioDevices() { 200 return mInputAudioDevice; 201 } 202 } 203