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