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