/* * Copyright (C) 2021 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 com.android.car.audio.hal.AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_GROUP_MUTING; import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; import android.annotation.NonNull; import android.hardware.automotive.audiocontrol.MutingInfo; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import com.android.car.CarLog; import com.android.car.audio.hal.AudioControlWrapper; import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.List; import java.util.Objects; final class CarVolumeGroupMuting { private static final String TAG = CarLog.tagFor(CarVolumeGroupMuting.class); private final SparseArray mCarAudioZones; private final AudioControlWrapper mAudioControlWrapper; private final Object mLock = new Object(); @GuardedBy("mLock") private List mLastMutingInformation; @GuardedBy("mLock") private boolean mIsMutingRestricted; CarVolumeGroupMuting(@NonNull SparseArray carAudioZones, @NonNull AudioControlWrapper audioControlWrapper) { mCarAudioZones = Objects.requireNonNull(carAudioZones, "Car Audio Zones can not be null"); Preconditions.checkArgument(carAudioZones.size() != 0, "At least one car audio zone must be present."); mAudioControlWrapper = Objects.requireNonNull(audioControlWrapper, "Audio Control Wrapper can not be null"); requireGroupMutingSupported(audioControlWrapper); mLastMutingInformation = new ArrayList<>(); } private static void requireGroupMutingSupported(AudioControlWrapper audioControlWrapper) { if (audioControlWrapper .supportsFeature(AUDIOCONTROL_FEATURE_AUDIO_GROUP_MUTING)) { return; } throw new IllegalStateException("audioUseCarVolumeGroupMuting is enabled but " + "IAudioControl HAL does not support volume group muting"); } /** * Signal that mute has changed. */ public void carMuteChanged() { if (Log.isLoggable(TAG, Log.DEBUG)) { Slog.d(TAG, "carMuteChanged"); } List mutingInfo = generateMutingInfo(); setLastMutingInfo(mutingInfo); mAudioControlWrapper.onDevicesToMuteChange(mutingInfo); } public void setRestrictMuting(boolean isMutingRestricted) { synchronized (mLock) { mIsMutingRestricted = isMutingRestricted; } carMuteChanged(); } private boolean isMutingRestricted() { synchronized (mLock) { return mIsMutingRestricted; } } private void setLastMutingInfo(List mutingInfo) { synchronized (mLock) { mLastMutingInformation = mutingInfo; } } @VisibleForTesting List getLastMutingInformation() { synchronized (mLock) { return mLastMutingInformation; } } private List generateMutingInfo() { List mutingInformation = new ArrayList<>(mCarAudioZones.size()); boolean isMutingRestricted = isMutingRestricted(); for (int index = 0; index < mCarAudioZones.size(); index++) { mutingInformation.add(generateMutingInfoFromZone(mCarAudioZones.valueAt(index), isMutingRestricted)); } return mutingInformation; } /** * Dumps internal state */ @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) public void dump(IndentingPrintWriter writer) { writer.println(TAG); writer.increaseIndent(); synchronized (mLock) { writer.printf("Is muting restricted? %b\n", mIsMutingRestricted); for (int index = 0; index < mLastMutingInformation.size(); index++) { dumpCarMutingInfo(writer, mLastMutingInformation.get(index)); } } writer.decreaseIndent(); } private void dumpCarMutingInfo(IndentingPrintWriter writer, MutingInfo info) { writer.printf("Zone ID: %d\n", info.zoneId); writer.println("Muted Devices:"); writer.increaseIndent(); dumpDeviceAddresses(writer, info.deviceAddressesToMute); writer.decreaseIndent(); writer.println("Un-muted Devices:"); writer.increaseIndent(); dumpDeviceAddresses(writer, info.deviceAddressesToUnmute); writer.decreaseIndent(); } private static void dumpDeviceAddresses(IndentingPrintWriter writer, String[] devices) { for (int index = 0; index < devices.length; index++) { writer.printf("%d %s\n", index, devices[index]); } } @VisibleForTesting static MutingInfo generateMutingInfoFromZone(CarAudioZone audioZone, boolean isMutingRestricted) { MutingInfo mutingInfo = new MutingInfo(); mutingInfo.zoneId = audioZone.getId(); List mutedDevices = new ArrayList<>(); List unMutedDevices = new ArrayList<>(); CarVolumeGroup[] groups = audioZone.getVolumeGroups(); for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { CarVolumeGroup group = groups[groupIndex]; if (group.isMuted() || (isMutingRestricted && !group.hasCriticalAudioContexts())) { mutedDevices.addAll(group.getAddresses()); } else { unMutedDevices.addAll(group.getAddresses()); } } mutingInfo.deviceAddressesToMute = mutedDevices.toArray(new String[mutedDevices.size()]); mutingInfo.deviceAddressesToUnmute = unMutedDevices.toArray(new String[unMutedDevices.size()]); return mutingInfo; } }