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.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.car.media.CarAudioManager; 21 import android.content.ContentResolver; 22 import android.content.Context; 23 import android.hardware.automotive.audiocontrol.V1_0.ContextNumber; 24 import android.media.AudioDevicePort; 25 import android.provider.Settings; 26 import android.util.SparseArray; 27 import android.util.SparseIntArray; 28 29 import com.android.internal.util.Preconditions; 30 31 import java.io.PrintWriter; 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.List; 35 36 /** 37 * A class encapsulates a volume group in car. 38 * 39 * Volume in a car is controlled by group. A group holds one or more car audio contexts. 40 * Call {@link CarAudioManager#getVolumeGroupCount()} to get the count of {@link CarVolumeGroup} 41 * supported in a car. 42 */ 43 /* package */ final class CarVolumeGroup { 44 45 private final ContentResolver mContentResolver; 46 private final int mZoneId; 47 private final int mId; 48 private final SparseIntArray mContextToBus = new SparseIntArray(); 49 private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfo = new SparseArray<>(); 50 51 private int mDefaultGain = Integer.MIN_VALUE; 52 private int mMaxGain = Integer.MIN_VALUE; 53 private int mMinGain = Integer.MAX_VALUE; 54 private int mStepSize = 0; 55 private int mStoredGainIndex; 56 private int mCurrentGainIndex = -1; 57 58 /** 59 * Constructs a {@link CarVolumeGroup} instance 60 * @param context {@link Context} instance 61 * @param zoneId Audio zone this volume group belongs to 62 * @param id ID of this volume group 63 */ CarVolumeGroup(Context context, int zoneId, int id)64 CarVolumeGroup(Context context, int zoneId, int id) { 65 mContentResolver = context.getContentResolver(); 66 mZoneId = zoneId; 67 mId = id; 68 mStoredGainIndex = Settings.Global.getInt(mContentResolver, 69 CarAudioService.getVolumeSettingsKeyForGroup(mZoneId, mId), -1); 70 } 71 72 /** 73 * Constructs a {@link CarVolumeGroup} instance 74 * @param context {@link Context} instance 75 * @param zoneId Audio zone this volume group belongs to 76 * @param id ID of this volume group 77 * @param contexts Pre-populated array of car contexts, for legacy car_volume_groups.xml only 78 * @deprecated In favor of {@link #CarVolumeGroup(Context, int, int)} 79 */ 80 @Deprecated CarVolumeGroup(Context context, int zoneId, int id, @NonNull int[] contexts)81 CarVolumeGroup(Context context, int zoneId, int id, @NonNull int[] contexts) { 82 this(context, zoneId, id); 83 // Deal with the pre-populated car audio contexts 84 for (int audioContext : contexts) { 85 mContextToBus.put(audioContext, -1); 86 } 87 } 88 89 /** 90 * @param busNumber Physical bus number for the audio device port 91 * @return {@link CarAudioDeviceInfo} associated with a given bus number 92 */ getCarAudioDeviceInfoForBus(int busNumber)93 CarAudioDeviceInfo getCarAudioDeviceInfoForBus(int busNumber) { 94 return mBusToCarAudioDeviceInfo.get(busNumber); 95 } 96 97 /** 98 * @return Array of context numbers in this {@link CarVolumeGroup} 99 */ getContexts()100 int[] getContexts() { 101 final int[] contextNumbers = new int[mContextToBus.size()]; 102 for (int i = 0; i < contextNumbers.length; i++) { 103 contextNumbers[i] = mContextToBus.keyAt(i); 104 } 105 return contextNumbers; 106 } 107 108 /** 109 * @param busNumber Physical bus number for the audio device port 110 * @return Array of context numbers assigned to a given bus number 111 */ getContextsForBus(int busNumber)112 int[] getContextsForBus(int busNumber) { 113 List<Integer> contextNumbers = new ArrayList<>(); 114 for (int i = 0; i < mContextToBus.size(); i++) { 115 int value = mContextToBus.valueAt(i); 116 if (value == busNumber) { 117 contextNumbers.add(mContextToBus.keyAt(i)); 118 } 119 } 120 return contextNumbers.stream().mapToInt(i -> i).toArray(); 121 } 122 123 /** 124 * @return Array of bus numbers in this {@link CarVolumeGroup} 125 */ getBusNumbers()126 int[] getBusNumbers() { 127 final int[] busNumbers = new int[mBusToCarAudioDeviceInfo.size()]; 128 for (int i = 0; i < busNumbers.length; i++) { 129 busNumbers[i] = mBusToCarAudioDeviceInfo.keyAt(i); 130 } 131 return busNumbers; 132 } 133 134 /** 135 * Binds the context number to physical bus number and audio device port information. 136 * Because this may change the groups min/max values, thus invalidating an index computed from 137 * a gain before this call, all calls to this function must happen at startup before any 138 * set/getGainIndex calls. 139 * 140 * @param contextNumber Context number as defined in audio control HAL 141 * @param busNumber Physical bus number for the audio device port 142 * @param info {@link CarAudioDeviceInfo} instance relates to the physical bus 143 */ bind(int contextNumber, int busNumber, CarAudioDeviceInfo info)144 void bind(int contextNumber, int busNumber, CarAudioDeviceInfo info) { 145 if (mBusToCarAudioDeviceInfo.size() == 0) { 146 mStepSize = info.getAudioGain().stepValue(); 147 } else { 148 Preconditions.checkArgument( 149 info.getAudioGain().stepValue() == mStepSize, 150 "Gain controls within one group must have same step value"); 151 } 152 153 mContextToBus.put(contextNumber, busNumber); 154 mBusToCarAudioDeviceInfo.put(busNumber, info); 155 156 if (info.getDefaultGain() > mDefaultGain) { 157 // We're arbitrarily selecting the highest bus default gain as the group's default. 158 mDefaultGain = info.getDefaultGain(); 159 } 160 if (info.getMaxGain() > mMaxGain) { 161 mMaxGain = info.getMaxGain(); 162 } 163 if (info.getMinGain() < mMinGain) { 164 mMinGain = info.getMinGain(); 165 } 166 if (mStoredGainIndex < getMinGainIndex() || mStoredGainIndex > getMaxGainIndex()) { 167 // We expected to load a value from last boot, but if we didn't (perhaps this is the 168 // first boot ever?), then use the highest "default" we've seen to initialize 169 // ourselves. 170 mCurrentGainIndex = getIndexForGain(mDefaultGain); 171 } else { 172 // Just use the gain index we stored last time the gain was set (presumably during our 173 // last boot cycle). 174 mCurrentGainIndex = mStoredGainIndex; 175 } 176 } 177 getDefaultGainIndex()178 private int getDefaultGainIndex() { 179 return getIndexForGain(mDefaultGain); 180 } 181 getMaxGainIndex()182 int getMaxGainIndex() { 183 return getIndexForGain(mMaxGain); 184 } 185 getMinGainIndex()186 int getMinGainIndex() { 187 return getIndexForGain(mMinGain); 188 } 189 getCurrentGainIndex()190 int getCurrentGainIndex() { 191 return mCurrentGainIndex; 192 } 193 194 /** 195 * Sets the gain on this group, gain will be set on all buses within same bus. 196 * @param gainIndex The gain index 197 */ setCurrentGainIndex(int gainIndex)198 void setCurrentGainIndex(int gainIndex) { 199 int gainInMillibels = getGainForIndex(gainIndex); 200 201 Preconditions.checkArgument( 202 gainInMillibels >= mMinGain && gainInMillibels <= mMaxGain, 203 "Gain out of range (" 204 + mMinGain + ":" 205 + mMaxGain + ") " 206 + gainInMillibels + "index " 207 + gainIndex); 208 209 for (int i = 0; i < mBusToCarAudioDeviceInfo.size(); i++) { 210 CarAudioDeviceInfo info = mBusToCarAudioDeviceInfo.valueAt(i); 211 info.setCurrentGain(gainInMillibels); 212 } 213 214 mCurrentGainIndex = gainIndex; 215 Settings.Global.putInt(mContentResolver, 216 CarAudioService.getVolumeSettingsKeyForGroup(mZoneId, mId), gainIndex); 217 } 218 219 // Given a group level gain index, return the computed gain in millibells 220 // TODO (randolphs) If we ever want to add index to gain curves other than lock-stepped 221 // linear, this would be the place to do it. getGainForIndex(int gainIndex)222 private int getGainForIndex(int gainIndex) { 223 return mMinGain + gainIndex * mStepSize; 224 } 225 226 // TODO (randolphs) if we ever went to a non-linear index to gain curve mapping, we'd need to 227 // revisit this as it assumes (at the least) that getGainForIndex is reversible. Luckily, 228 // this is an internal implementation details we could factor out if/when necessary. getIndexForGain(int gainInMillibel)229 private int getIndexForGain(int gainInMillibel) { 230 return (gainInMillibel - mMinGain) / mStepSize; 231 } 232 233 /** 234 * Gets {@link AudioDevicePort} from a context number 235 */ 236 @Nullable getAudioDevicePortForContext(int contextNumber)237 AudioDevicePort getAudioDevicePortForContext(int contextNumber) { 238 final int busNumber = mContextToBus.get(contextNumber, -1); 239 if (busNumber < 0 || mBusToCarAudioDeviceInfo.get(busNumber) == null) { 240 return null; 241 } 242 return mBusToCarAudioDeviceInfo.get(busNumber).getAudioDevicePort(); 243 } 244 245 @Override toString()246 public String toString() { 247 return "CarVolumeGroup id: " + mId 248 + " currentGainIndex: " + mCurrentGainIndex 249 + " contexts: " + Arrays.toString(getContexts()) 250 + " buses: " + Arrays.toString(getBusNumbers()); 251 } 252 253 /** Writes to dumpsys output */ dump(String indent, PrintWriter writer)254 void dump(String indent, PrintWriter writer) { 255 writer.printf("%sCarVolumeGroup(%d)\n", indent, mId); 256 writer.printf("%sGain values (min / max / default/ current): %d %d %d %d\n", 257 indent, mMinGain, mMaxGain, 258 mDefaultGain, getGainForIndex(mCurrentGainIndex)); 259 writer.printf("%sGain indexes (min / max / default / current): %d %d %d %d\n", 260 indent, getMinGainIndex(), getMaxGainIndex(), 261 getDefaultGainIndex(), mCurrentGainIndex); 262 for (int i = 0; i < mContextToBus.size(); i++) { 263 writer.printf("%sContext: %s -> Bus: %d\n", indent, 264 ContextNumber.toString(mContextToBus.keyAt(i)), mContextToBus.valueAt(i)); 265 } 266 for (int i = 0; i < mBusToCarAudioDeviceInfo.size(); i++) { 267 mBusToCarAudioDeviceInfo.valueAt(i).dump(indent, writer); 268 } 269 // Empty line for comfortable reading 270 writer.println(); 271 } 272 } 273