1 /* 2 * Copyright (C) 2022 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 android.annotation.NonNull; 20 import android.car.builtin.util.Slogf; 21 import android.car.media.CarVolumeGroupEvent; 22 import android.hardware.automotive.audiocontrol.Reasons; 23 import android.util.SparseArray; 24 import android.util.SparseIntArray; 25 26 import com.android.car.CarLog; 27 import com.android.car.audio.hal.AudioControlWrapper; 28 import com.android.car.audio.hal.HalAudioGainCallback; 29 30 import java.util.ArrayList; 31 import java.util.List; 32 import java.util.Objects; 33 34 /** 35 * Provides audio gain callback registration helpers and implements AudioGain listener business 36 * logic. 37 */ 38 /* package */ final class CarAudioGainMonitor { 39 private static final int INVALID_INFO = -1; 40 @NonNull private final AudioControlWrapper mAudioControlWrapper; 41 @NonNull private final SparseArray<CarAudioZone> mCarAudioZones; 42 private final CarVolumeInfoWrapper mCarVolumeInfoWrapper; 43 CarAudioGainMonitor(AudioControlWrapper audioControlWrapper, CarVolumeInfoWrapper carVolumeInfoWrapper, SparseArray<CarAudioZone> carAudioZones)44 CarAudioGainMonitor(AudioControlWrapper audioControlWrapper, 45 CarVolumeInfoWrapper carVolumeInfoWrapper, SparseArray<CarAudioZone> carAudioZones) { 46 mAudioControlWrapper = Objects.requireNonNull(audioControlWrapper, 47 "Audio Control Wrapper can not be null"); 48 mCarVolumeInfoWrapper = Objects.requireNonNull(carVolumeInfoWrapper, 49 "Car volume info wrapper can not be null"); 50 mCarAudioZones = Objects.requireNonNull(carAudioZones, "Car Audio Zones can not be null"); 51 } 52 reset()53 public void reset() { 54 // TODO (b/224885748): handle specific logic on IAudioControl service died event 55 } 56 57 /** 58 * Registers {@code HalAudioGainCallback} on {@code AudioControlWrapper} to receive HAL audio 59 * gain change notifications. 60 */ registerAudioGainListener(HalAudioGainCallback callback)61 public void registerAudioGainListener(HalAudioGainCallback callback) { 62 Objects.requireNonNull(callback, "Hal Audio Gain callback can not be null"); 63 mAudioControlWrapper.registerAudioGainCallback(callback); 64 } 65 66 /** Unregisters {@code HalAudioGainCallback} from {@code AudioControlWrapper}. */ unregisterAudioGainListener()67 public void unregisterAudioGainListener() { 68 mAudioControlWrapper.unregisterAudioGainCallback(); 69 } 70 71 /** 72 * Audio Gain event dispatcher. Implements the callback that triggered from {@link 73 * IAudioGainCallback#onAudioDeviceGainsChanged} with the list of reasons and the list of {@link 74 * CarAudioGainConfigInfo} involved. It is in charge of dispatching /delegating to the zone the 75 * {@link CarAudioGainConfigInfo} belongs the processing of the callback. 76 */ handleAudioDeviceGainsChanged(List<Integer> reasons, List<CarAudioGainConfigInfo> gains)77 void handleAudioDeviceGainsChanged(List<Integer> reasons, List<CarAudioGainConfigInfo> gains) { 78 List<CarVolumeGroupEvent> events = new ArrayList<>(); 79 // Delegate to CarAudioZone / CarVolumeGroup 80 // Group gains by Audio Zones first 81 SparseArray<List<CarAudioGainConfigInfo>> gainsByZones = new SparseArray<>(); 82 for (int index = 0; index < gains.size(); index++) { 83 CarAudioGainConfigInfo gain = gains.get(index); 84 int zone = gain.getZoneId(); 85 if (!gainsByZones.contains(zone)) { 86 gainsByZones.put(zone, new ArrayList<>(1)); 87 } 88 gainsByZones.get(zone).add(gain); 89 } 90 for (int i = 0; i < gainsByZones.size(); i++) { 91 int zoneId = gainsByZones.keyAt(i); 92 if (!mCarAudioZones.contains(zoneId)) { 93 Slogf.e( 94 CarLog.TAG_AUDIO, 95 "onAudioDeviceGainsChanged reported change on invalid " 96 + "zone: %d, reasons=%s, gains=%s", 97 zoneId, 98 reasons, 99 gains); 100 continue; 101 } 102 CarAudioZone carAudioZone = mCarAudioZones.get(zoneId); 103 events.addAll(carAudioZone.onAudioGainChanged(reasons, gainsByZones.valueAt(i))); 104 } 105 106 // its possible we received redundant callbacks from hal. In such cases, 107 // do not call listeners with empty events. 108 if (events.isEmpty()) { 109 Slogf.w(CarLog.TAG_AUDIO, "Audio gain config callback resulted in no events!"); 110 return; 111 } 112 mCarVolumeInfoWrapper.onVolumeGroupEvent(events); 113 } 114 shouldBlockVolumeRequest(List<Integer> reasons)115 static boolean shouldBlockVolumeRequest(List<Integer> reasons) { 116 return reasons.contains(Reasons.FORCED_MASTER_MUTE) || reasons.contains(Reasons.TCU_MUTE) 117 || reasons.contains(Reasons.REMOTE_MUTE); 118 } 119 shouldLimitVolume(List<Integer> reasons)120 static boolean shouldLimitVolume(List<Integer> reasons) { 121 return reasons.contains(Reasons.THERMAL_LIMITATION) 122 || reasons.contains(Reasons.SUSPEND_EXIT_VOL_LIMITATION); 123 } 124 shouldDuckGain(List<Integer> reasons)125 static boolean shouldDuckGain(List<Integer> reasons) { 126 return reasons.contains(Reasons.ADAS_DUCKING) || reasons.contains(Reasons.NAV_DUCKING); 127 } 128 shouldMuteVolumeGroup(List<Integer> reasons)129 static boolean shouldMuteVolumeGroup(List<Integer> reasons) { 130 return reasons.contains(Reasons.TCU_MUTE) || reasons.contains(Reasons.REMOTE_MUTE); 131 } 132 shouldUpdateVolumeIndex(List<Integer> reasons)133 static boolean shouldUpdateVolumeIndex(List<Integer> reasons) { 134 return reasons.contains(Reasons.EXTERNAL_AMP_VOL_FEEDBACK); 135 } 136 137 private static final SparseIntArray REASONS_TO_EXTRA_INFO = new SparseIntArray(); 138 139 // note: Reasons.FORCED_MASTER_MUTE, Reasons.OTHER are not supported by CarVolumeGroupEvent 140 // extra-infos. Builder will automatically append EXTRA_INFO_NONE for these cases. 141 static { REASONS_TO_EXTRA_INFO.put(Reasons.REMOTE_MUTE, CarVolumeGroupEvent.EXTRA_INFO_MUTE_TOGGLED_BY_AUDIO_SYSTEM)142 REASONS_TO_EXTRA_INFO.put(Reasons.REMOTE_MUTE, 143 CarVolumeGroupEvent.EXTRA_INFO_MUTE_TOGGLED_BY_AUDIO_SYSTEM); REASONS_TO_EXTRA_INFO.put(Reasons.TCU_MUTE, CarVolumeGroupEvent.EXTRA_INFO_MUTE_TOGGLED_BY_EMERGENCY)144 REASONS_TO_EXTRA_INFO.put(Reasons.TCU_MUTE, 145 CarVolumeGroupEvent.EXTRA_INFO_MUTE_TOGGLED_BY_EMERGENCY); REASONS_TO_EXTRA_INFO.put(Reasons.ADAS_DUCKING, CarVolumeGroupEvent.EXTRA_INFO_TRANSIENT_ATTENUATION_EXTERNAL)146 REASONS_TO_EXTRA_INFO.put(Reasons.ADAS_DUCKING, 147 CarVolumeGroupEvent.EXTRA_INFO_TRANSIENT_ATTENUATION_EXTERNAL); REASONS_TO_EXTRA_INFO.put(Reasons.NAV_DUCKING, CarVolumeGroupEvent.EXTRA_INFO_TRANSIENT_ATTENUATION_NAVIGATION)148 REASONS_TO_EXTRA_INFO.put(Reasons.NAV_DUCKING, 149 CarVolumeGroupEvent.EXTRA_INFO_TRANSIENT_ATTENUATION_NAVIGATION); REASONS_TO_EXTRA_INFO.put(Reasons.PROJECTION_DUCKING, CarVolumeGroupEvent.EXTRA_INFO_TRANSIENT_ATTENUATION_PROJECTION)150 REASONS_TO_EXTRA_INFO.put(Reasons.PROJECTION_DUCKING, 151 CarVolumeGroupEvent.EXTRA_INFO_TRANSIENT_ATTENUATION_PROJECTION); REASONS_TO_EXTRA_INFO.put(Reasons.THERMAL_LIMITATION, CarVolumeGroupEvent.EXTRA_INFO_TRANSIENT_ATTENUATION_THERMAL)152 REASONS_TO_EXTRA_INFO.put(Reasons.THERMAL_LIMITATION, 153 CarVolumeGroupEvent.EXTRA_INFO_TRANSIENT_ATTENUATION_THERMAL); REASONS_TO_EXTRA_INFO.put(Reasons.SUSPEND_EXIT_VOL_LIMITATION, CarVolumeGroupEvent.EXTRA_INFO_ATTENUATION_ACTIVATION)154 REASONS_TO_EXTRA_INFO.put(Reasons.SUSPEND_EXIT_VOL_LIMITATION, 155 CarVolumeGroupEvent.EXTRA_INFO_ATTENUATION_ACTIVATION); REASONS_TO_EXTRA_INFO.put(Reasons.EXTERNAL_AMP_VOL_FEEDBACK, CarVolumeGroupEvent.EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_AUDIO_SYSTEM)156 REASONS_TO_EXTRA_INFO.put(Reasons.EXTERNAL_AMP_VOL_FEEDBACK, 157 CarVolumeGroupEvent.EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_AUDIO_SYSTEM); 158 } 159 convertReasonsToExtraInfo(List<Integer> reasons)160 static List<Integer> convertReasonsToExtraInfo(List<Integer> reasons) { 161 List<Integer> extraInfos = new ArrayList<>(); 162 for (int index = 0; index < reasons.size(); index++) { 163 // cannot assume validaty of reason. 164 // also not all reasons are supported in extra-info 165 int extraInfo = REASONS_TO_EXTRA_INFO.get(reasons.get(index), INVALID_INFO); 166 if (extraInfo != INVALID_INFO) { 167 extraInfos.add(extraInfo); 168 } 169 } 170 return extraInfos; 171 } 172 } 173