/* * Copyright (C) 2023 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 static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED; import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED; import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED; import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; import android.util.SparseArray; import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; import com.android.car.internal.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; /** * A class encapsulates a volume group in car. * * Volume in a car is controlled by group. A group holds one or more car audio contexts. * Call {@link CarAudioManager#getVolumeGroupCount()} to get the count of {@link CarVolumeGroup} * supported in a car. */ final class CarAudioVolumeGroup extends CarVolumeGroup { private static final int UNSET_STEP_SIZE = -1; private static final int EVENT_TYPE_NONE = 0; @GuardedBy("mLock") private int mDefaultGain = Integer.MAX_VALUE; @GuardedBy("mLock") private int mMaxGain = Integer.MIN_VALUE; @GuardedBy("mLock") private int mMinGain = Integer.MAX_VALUE; @GuardedBy("mLock") private int mStepSize = UNSET_STEP_SIZE; CarAudioVolumeGroup(CarAudioContext carAudioContext, CarAudioSettings settingsManager, SparseArray contextToDeviceInfo, int zoneId, int configId, int volumeGroupId, String name, int stepSize, int defaultGain, int minGain, int maxGain, boolean useCarVolumeGroupMute, CarActivationVolumeConfig carActivationVolumeConfig) { super(carAudioContext, settingsManager, contextToDeviceInfo, zoneId, configId, volumeGroupId, name, useCarVolumeGroupMute, carActivationVolumeConfig); Preconditions.checkArgument(stepSize != 0, "Step Size must not be zero"); mStepSize = stepSize; mDefaultGain = defaultGain; mMinGain = minGain; mMaxGain = maxGain; mLimitedGainIndex = getIndexForGainLocked(mMaxGain); } @Override public int getMaxGainIndex() { synchronized (mLock) { return getIndexForGainLocked(mMaxGain); } } @Override public int getMinGainIndex() { synchronized (mLock) { return getIndexForGainLocked(mMinGain); } } @Override @GuardedBy("mLock") @SuppressWarnings("GuardedBy") protected void setCurrentGainIndexLocked(int gainIndex) { int gainInMillibels = getGainForIndexLocked(gainIndex); for (int index = 0; index < mAddressToCarAudioDeviceInfo.size(); index++) { CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.valueAt(index); info.setCurrentGain(gainInMillibels); } super.setCurrentGainIndexLocked(gainIndex); } @Override @GuardedBy("mLock") protected boolean isValidGainIndexLocked(int gainIndex) { return gainIndex >= getIndexForGainLocked(mMinGain) && gainIndex <= getIndexForGainLocked(mMaxGain); } @Override @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) @GuardedBy("mLock") protected void dumpLocked(IndentingPrintWriter writer) { writer.printf("Step size: %d\n", mStepSize); writer.printf("Gain values (min / max / default/ current): %d %d %d %d\n", mMinGain, mMaxGain, mDefaultGain, getGainForIndexLocked(mCurrentGainIndex)); } @Override protected int getDefaultGainIndex() { synchronized (mLock) { return getIndexForGainLocked(mDefaultGain); } } @GuardedBy("mLock") private int getGainForIndexLocked(int gainIndex) { return mMinGain + gainIndex * mStepSize; } @GuardedBy("mLock") private int getIndexForGainLocked(int gainInMillibel) { return (gainInMillibel - mMinGain) / mStepSize; } @Override int calculateNewGainStageFromDeviceInfos() { int minGain = Integer.MAX_VALUE; int maxGain = Integer.MIN_VALUE; int defaultGain = Integer.MAX_VALUE; int stepSize = UNSET_STEP_SIZE; int eventType = EVENT_TYPE_NONE; synchronized (mLock) { // compute the new volume group gain stage from scratch for (int index = 0; index < mAddressToCarAudioDeviceInfo.size(); index++) { CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.valueAt(index); if (stepSize == UNSET_STEP_SIZE) { stepSize = info.getStepValue(); } else { Preconditions.checkArgument( info.getStepValue() == stepSize, "Gain stages within one group must have same step value"); } if (info.getDefaultGain() < defaultGain) { // We're arbitrarily selecting the lowest // device default gain as the group's default. defaultGain = info.getDefaultGain(); } if (info.getMaxGain() > maxGain) { maxGain = info.getMaxGain(); } if (info.getMinGain() < minGain) { minGain = info.getMinGain(); } } // update the new gain stage and return event types so that callback can be triggered. // get the current and restricted gains in mb before updating the volume bounds. These // will be used for extrapolating to new volume ranges int epCurrentGainInMb = getGainForIndexLocked(getCurrentGainIndexLocked()); int epLimitedGainInMb = getGainForIndexLocked(mLimitedGainIndex); int epBlockedGainInMb = getGainForIndexLocked(mBlockedGainIndex); int epAttenuatedGainInMb = getGainForIndexLocked(mAttenuatedGainIndex); boolean isLimited = isLimitedLocked(); // update the volume ranges if (minGain != mMinGain) { mMinGain = minGain; eventType |= (EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED | EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED); } if (maxGain != mMaxGain) { mMaxGain = maxGain; eventType |= (EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED | EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED); } // if only default gain changes, no impact to volume gain stages (i.e. no event). if (defaultGain != mDefaultGain) { mDefaultGain = defaultGain; } if (stepSize != mStepSize) { mStepSize = stepSize; eventType |= (EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED | EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED); } // if no change to indexes, return if (eventType == EVENT_TYPE_NONE) { return eventType; } // if min/max/step values change, we shall try to maintain the same volume level // through simple extrapolation, i.e., the new gain index is calculated from the // previous {@code mCurrentGainIndex} in millibels. // caution: before updating, check if the previous gain is out of bound in the // new gain stage. If yes, use the safe value (i.e default gain) // provided by the hal implementations. mCurrentGainIndex = getIndexForGainLocked(epCurrentGainInMb); if (!isValidGainIndexLocked(mCurrentGainIndex)) { mCurrentGainIndex = getIndexForGainLocked(mDefaultGain); } // similar extrapolation is tried for restricted gains: limited, blocked, attenuated. // Note: Even after best effort, it is possible that some or all of the old restriction // indexes are invalid and therefore reset. int newLimitedGainIndex = getIndexForGainLocked(epLimitedGainInMb); if (isLimited && isValidGainIndexLocked(newLimitedGainIndex)) { setLimitLocked(newLimitedGainIndex); } else { resetLimitLocked(); } int newBlockedGainIndex = getIndexForGainLocked(epBlockedGainInMb); if (isBlockedLocked() && isValidGainIndexLocked(newBlockedGainIndex)) { setBlockedLocked(newBlockedGainIndex); } else { resetBlockedLocked(); } int newAttenuatedGainIndex = getIndexForGainLocked(epAttenuatedGainInMb); if (isAttenuatedLocked() && isValidGainIndexLocked(newAttenuatedGainIndex)) { setAttenuatedGainLocked(newAttenuatedGainIndex); } else { resetAttenuationLocked(); } // Notes: // (1) Setting current gain index will trigger Audio HAL. If restrictions are still // valid but were reset above, we expect AudioControl HAL to resend the restrictions // in a callback. // (2) Audio HAL will be responsible to ensure consistent speaker output during this // transition. setCurrentGainIndexLocked(getRestrictedGainForIndexLocked(mCurrentGainIndex)); } return eventType; } }