1 /* 2 * Copyright (C) 2021 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.audio; 18 19 import static com.android.car.audio.hal.AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_GROUP_MUTING; 20 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 21 22 import android.annotation.NonNull; 23 import android.hardware.automotive.audiocontrol.MutingInfo; 24 import android.util.IndentingPrintWriter; 25 import android.util.Log; 26 import android.util.Slog; 27 import android.util.SparseArray; 28 29 import com.android.car.CarLog; 30 import com.android.car.audio.hal.AudioControlWrapper; 31 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 32 import com.android.internal.annotations.GuardedBy; 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.internal.util.Preconditions; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.Objects; 39 40 final class CarVolumeGroupMuting { 41 42 private static final String TAG = CarLog.tagFor(CarVolumeGroupMuting.class); 43 44 private final SparseArray<CarAudioZone> mCarAudioZones; 45 private final AudioControlWrapper mAudioControlWrapper; 46 private final Object mLock = new Object(); 47 @GuardedBy("mLock") 48 private List<MutingInfo> mLastMutingInformation; 49 @GuardedBy("mLock") 50 private boolean mIsMutingRestricted; 51 CarVolumeGroupMuting(@onNull SparseArray<CarAudioZone> carAudioZones, @NonNull AudioControlWrapper audioControlWrapper)52 CarVolumeGroupMuting(@NonNull SparseArray<CarAudioZone> carAudioZones, 53 @NonNull AudioControlWrapper audioControlWrapper) { 54 mCarAudioZones = Objects.requireNonNull(carAudioZones, "Car Audio Zones can not be null"); 55 Preconditions.checkArgument(carAudioZones.size() != 0, 56 "At least one car audio zone must be present."); 57 mAudioControlWrapper = Objects.requireNonNull(audioControlWrapper, 58 "Audio Control Wrapper can not be null"); 59 requireGroupMutingSupported(audioControlWrapper); 60 mLastMutingInformation = new ArrayList<>(); 61 } 62 requireGroupMutingSupported(AudioControlWrapper audioControlWrapper)63 private static void requireGroupMutingSupported(AudioControlWrapper audioControlWrapper) { 64 if (audioControlWrapper 65 .supportsFeature(AUDIOCONTROL_FEATURE_AUDIO_GROUP_MUTING)) { 66 return; 67 } 68 throw new IllegalStateException("audioUseCarVolumeGroupMuting is enabled but " 69 + "IAudioControl HAL does not support volume group muting"); 70 } 71 72 /** 73 * Signal that mute has changed. 74 */ carMuteChanged()75 public void carMuteChanged() { 76 if (Log.isLoggable(TAG, Log.DEBUG)) { 77 Slog.d(TAG, "carMuteChanged"); 78 } 79 80 List<MutingInfo> mutingInfo = generateMutingInfo(); 81 setLastMutingInfo(mutingInfo); 82 mAudioControlWrapper.onDevicesToMuteChange(mutingInfo); 83 } 84 setRestrictMuting(boolean isMutingRestricted)85 public void setRestrictMuting(boolean isMutingRestricted) { 86 synchronized (mLock) { 87 mIsMutingRestricted = isMutingRestricted; 88 } 89 90 carMuteChanged(); 91 } 92 isMutingRestricted()93 private boolean isMutingRestricted() { 94 synchronized (mLock) { 95 return mIsMutingRestricted; 96 } 97 } 98 setLastMutingInfo(List<MutingInfo> mutingInfo)99 private void setLastMutingInfo(List<MutingInfo> mutingInfo) { 100 synchronized (mLock) { 101 mLastMutingInformation = mutingInfo; 102 } 103 } 104 105 @VisibleForTesting getLastMutingInformation()106 List<MutingInfo> getLastMutingInformation() { 107 synchronized (mLock) { 108 return mLastMutingInformation; 109 } 110 } 111 generateMutingInfo()112 private List<MutingInfo> generateMutingInfo() { 113 List<MutingInfo> mutingInformation = new ArrayList<>(mCarAudioZones.size()); 114 115 boolean isMutingRestricted = isMutingRestricted(); 116 for (int index = 0; index < mCarAudioZones.size(); index++) { 117 mutingInformation.add(generateMutingInfoFromZone(mCarAudioZones.valueAt(index), 118 isMutingRestricted)); 119 } 120 121 return mutingInformation; 122 } 123 124 /** 125 * Dumps internal state 126 */ 127 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)128 public void dump(IndentingPrintWriter writer) { 129 writer.println(TAG); 130 writer.increaseIndent(); 131 synchronized (mLock) { 132 writer.printf("Is muting restricted? %b\n", mIsMutingRestricted); 133 for (int index = 0; index < mLastMutingInformation.size(); index++) { 134 dumpCarMutingInfo(writer, mLastMutingInformation.get(index)); 135 } 136 } 137 writer.decreaseIndent(); 138 } 139 dumpCarMutingInfo(IndentingPrintWriter writer, MutingInfo info)140 private void dumpCarMutingInfo(IndentingPrintWriter writer, MutingInfo info) { 141 writer.printf("Zone ID: %d\n", info.zoneId); 142 143 writer.println("Muted Devices:"); 144 writer.increaseIndent(); 145 dumpDeviceAddresses(writer, info.deviceAddressesToMute); 146 writer.decreaseIndent(); 147 148 writer.println("Un-muted Devices:"); 149 writer.increaseIndent(); 150 dumpDeviceAddresses(writer, info.deviceAddressesToUnmute); 151 writer.decreaseIndent(); 152 } 153 dumpDeviceAddresses(IndentingPrintWriter writer, String[] devices)154 private static void dumpDeviceAddresses(IndentingPrintWriter writer, String[] devices) { 155 for (int index = 0; index < devices.length; index++) { 156 writer.printf("%d %s\n", index, devices[index]); 157 } 158 } 159 160 @VisibleForTesting generateMutingInfoFromZone(CarAudioZone audioZone, boolean isMutingRestricted)161 static MutingInfo generateMutingInfoFromZone(CarAudioZone audioZone, 162 boolean isMutingRestricted) { 163 MutingInfo mutingInfo = new MutingInfo(); 164 mutingInfo.zoneId = audioZone.getId(); 165 166 List<String> mutedDevices = new ArrayList<>(); 167 List<String> unMutedDevices = new ArrayList<>(); 168 CarVolumeGroup[] groups = audioZone.getVolumeGroups(); 169 170 for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { 171 CarVolumeGroup group = groups[groupIndex]; 172 173 if (group.isMuted() || (isMutingRestricted && !group.hasCriticalAudioContexts())) { 174 mutedDevices.addAll(group.getAddresses()); 175 } else { 176 unMutedDevices.addAll(group.getAddresses()); 177 } 178 } 179 180 mutingInfo.deviceAddressesToMute = mutedDevices.toArray(new String[mutedDevices.size()]); 181 mutingInfo.deviceAddressesToUnmute = 182 unMutedDevices.toArray(new String[unMutedDevices.size()]); 183 184 return mutingInfo; 185 } 186 } 187