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.AudioDeviceInfo; 20 import android.util.Log; 21 import android.view.DisplayAddress; 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 final List<DisplayAddress.Physical> mPhysicalDisplayAddresses; 48 CarAudioZone(int id, String name)49 CarAudioZone(int id, String name) { 50 mId = id; 51 mName = name; 52 mVolumeGroups = new ArrayList<>(); 53 mPhysicalDisplayAddresses = 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 (int busNumber : group.getBusNumbers()) { 85 devices.add(group.getCarAudioDeviceInfoForBus(busNumber).getAudioDeviceInfo()); 86 } 87 } 88 return devices; 89 } 90 getVolumeGroupCount()91 int getVolumeGroupCount() { 92 return mVolumeGroups.size(); 93 } 94 95 /** 96 * Associates a new display physical port with this audio zone. This can be used to 97 * identify what zone an activity should produce sound in when launching on a particular display 98 * @param physicalDisplayAddress port to associate with this zone 99 */ addPhysicalDisplayAddress(DisplayAddress.Physical physicalDisplayAddress)100 void addPhysicalDisplayAddress(DisplayAddress.Physical physicalDisplayAddress) { 101 mPhysicalDisplayAddresses.add(physicalDisplayAddress); 102 } 103 104 /** 105 * Gets list of ports for displays associated with this audio zone 106 * @return list of Physical ports for displays associated with this audio zone 107 */ getPhysicalDisplayAddresses()108 List<DisplayAddress.Physical> getPhysicalDisplayAddresses() { 109 return mPhysicalDisplayAddresses; 110 } 111 112 /** 113 * @return Snapshot of available {@link CarVolumeGroup}s in array. 114 */ getVolumeGroups()115 CarVolumeGroup[] getVolumeGroups() { 116 return mVolumeGroups.toArray(new CarVolumeGroup[0]); 117 } 118 119 /** 120 * Constraints applied here: 121 * 122 * - One context should not appear in two groups 123 * - All contexts are assigned 124 * - One bus should not appear in two groups 125 * - All gain controllers in the same group have same step value 126 * 127 * Note that it is fine that there are buses not appear in any group, those buses may be 128 * reserved for other usages. 129 * Step value validation is done in {@link CarVolumeGroup#bind(int, int, CarAudioDeviceInfo)} 130 */ validateVolumeGroups()131 boolean validateVolumeGroups() { 132 Set<Integer> contextSet = new HashSet<>(); 133 Set<Integer> busNumberSet = new HashSet<>(); 134 for (CarVolumeGroup group : mVolumeGroups) { 135 // One context should not appear in two groups 136 for (int context : group.getContexts()) { 137 if (contextSet.contains(context)) { 138 Log.e(CarLog.TAG_AUDIO, "Context appears in two groups: " + context); 139 return false; 140 } 141 contextSet.add(context); 142 } 143 144 // One bus should not appear in two groups 145 for (int busNumber : group.getBusNumbers()) { 146 if (busNumberSet.contains(busNumber)) { 147 Log.e(CarLog.TAG_AUDIO, "Bus appears in two groups: " + busNumber); 148 return false; 149 } 150 busNumberSet.add(busNumber); 151 } 152 } 153 154 // All contexts are assigned 155 if (contextSet.size() != CarAudioDynamicRouting.CONTEXT_NUMBERS.length) { 156 Log.e(CarLog.TAG_AUDIO, "Some contexts are not assigned to group"); 157 Log.e(CarLog.TAG_AUDIO, "Assigned contexts " 158 + Arrays.toString(contextSet.toArray(new Integer[0]))); 159 Log.e(CarLog.TAG_AUDIO, 160 "All contexts " + Arrays.toString(CarAudioDynamicRouting.CONTEXT_NUMBERS)); 161 return false; 162 } 163 164 return true; 165 } 166 synchronizeCurrentGainIndex()167 void synchronizeCurrentGainIndex() { 168 for (CarVolumeGroup group : mVolumeGroups) { 169 group.setCurrentGainIndex(group.getCurrentGainIndex()); 170 } 171 } 172 dump(String indent, PrintWriter writer)173 void dump(String indent, PrintWriter writer) { 174 writer.printf("%sCarAudioZone(%s:%d) isPrimary? %b\n", indent, mName, mId, isPrimaryZone()); 175 for (DisplayAddress.Physical physical: mPhysicalDisplayAddresses) { 176 long port = (long) physical.getPort(); 177 writer.printf("%sDisplayAddress.Physical(%d)\n", indent + "\t", port); 178 } 179 writer.println(); 180 181 for (CarVolumeGroup group : mVolumeGroups) { 182 group.dump(indent + "\t", writer); 183 } 184 writer.println(); 185 } 186 } 187