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.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 20 21 import android.car.media.CarVolumeGroupInfo; 22 import android.car.oem.OemCarAudioVolumeRequest; 23 import android.media.AudioAttributes; 24 import android.media.AudioFocusInfo; 25 import android.util.SparseArray; 26 import android.util.proto.ProtoOutputStream; 27 28 import com.android.car.CarLocalServices; 29 import com.android.car.audio.CarZonesAudioFocus.CarFocusCallback; 30 import com.android.car.audio.hal.AudioControlWrapper; 31 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 32 import com.android.car.internal.util.IndentingPrintWriter; 33 import com.android.car.oem.CarOemProxyService; 34 import com.android.internal.annotations.GuardedBy; 35 import com.android.internal.annotations.VisibleForTesting; 36 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.Objects; 40 41 final class CarDucking implements CarFocusCallback { 42 private static final String TAG = CarDucking.class.getSimpleName(); 43 44 private final SparseArray<CarAudioZone> mCarAudioZones; 45 private final AudioControlWrapper mAudioControlWrapper; 46 private final Object mLock = new Object(); 47 48 @GuardedBy("mLock") 49 private final SparseArray<CarDuckingInfo> mCurrentDuckingInfo = new SparseArray<>(); 50 CarDucking(SparseArray<CarAudioZone> carAudioZones, AudioControlWrapper audioControlWrapper)51 CarDucking(SparseArray<CarAudioZone> carAudioZones, AudioControlWrapper audioControlWrapper) { 52 mCarAudioZones = Objects.requireNonNull(carAudioZones, "Car audio zones can not be null"); 53 mAudioControlWrapper = Objects.requireNonNull(audioControlWrapper, 54 "Audio control wrapper can not be null"); 55 56 for (int i = 0; i < carAudioZones.size(); i++) { 57 int zoneId = carAudioZones.keyAt(i); 58 mCurrentDuckingInfo.put( 59 zoneId, 60 new CarDuckingInfo( 61 zoneId, new ArrayList<>(), new ArrayList<>(), new ArrayList<>())); 62 } 63 } 64 65 @VisibleForTesting getCurrentDuckingInfo()66 SparseArray<CarDuckingInfo> getCurrentDuckingInfo() { 67 synchronized (mLock) { 68 return mCurrentDuckingInfo; 69 } 70 } 71 72 @Override onFocusChange(int[] audioZoneIds, SparseArray<List<AudioFocusInfo>> focusHoldersByZoneId)73 public void onFocusChange(int[] audioZoneIds, 74 SparseArray<List<AudioFocusInfo>> focusHoldersByZoneId) { 75 synchronized (mLock) { 76 List<CarDuckingInfo> newDuckingInfos = new ArrayList<>(audioZoneIds.length); 77 for (int i = 0; i < audioZoneIds.length; i++) { 78 int zoneId = audioZoneIds[i]; 79 List<AudioFocusInfo> focusHolders = focusHoldersByZoneId.get(zoneId); 80 CarDuckingInfo newDuckingInfo = updateDuckingForZoneIdLocked(zoneId, focusHolders); 81 newDuckingInfos.add(newDuckingInfo); 82 } 83 mAudioControlWrapper.onDevicesToDuckChange(newDuckingInfos); 84 } 85 } 86 87 @GuardedBy("mLock") updateDuckingForZoneIdLocked(int zoneId, List<AudioFocusInfo> focusHolders)88 private CarDuckingInfo updateDuckingForZoneIdLocked(int zoneId, 89 List<AudioFocusInfo> focusHolders) { 90 CarDuckingInfo oldDuckingInfo = mCurrentDuckingInfo.get(zoneId); 91 CarDuckingInfo newDuckingInfo = generateNewDuckingInfoLocked(oldDuckingInfo, 92 focusHolders); 93 mCurrentDuckingInfo.put(zoneId, newDuckingInfo); 94 return newDuckingInfo; 95 } 96 97 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)98 public void dump(IndentingPrintWriter writer) { 99 writer.printf("*%s*\n", TAG); 100 writer.increaseIndent(); 101 synchronized (mLock) { 102 for (int i = 0; i < mCurrentDuckingInfo.size(); i++) { 103 mCurrentDuckingInfo.valueAt(i).dump(writer); 104 } 105 } 106 writer.decreaseIndent(); 107 } 108 109 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)110 public void dumpProto(ProtoOutputStream proto) { 111 long carDuckingProto = proto.start(CarAudioDumpProto.CAR_DUCKING); 112 synchronized (mLock) { 113 for (int i = 0; i < mCurrentDuckingInfo.size(); i++) { 114 mCurrentDuckingInfo.valueAt(i).dumpProto(proto); 115 } 116 } 117 proto.end(carDuckingProto); 118 } 119 120 @GuardedBy("mLock") generateNewDuckingInfoLocked(CarDuckingInfo oldDuckingInfo, List<AudioFocusInfo> focusHolders)121 private CarDuckingInfo generateNewDuckingInfoLocked(CarDuckingInfo oldDuckingInfo, 122 List<AudioFocusInfo> focusHolders) { 123 int zoneId = oldDuckingInfo.mZoneId; 124 CarAudioZone zone = mCarAudioZones.get(zoneId); 125 126 List<CarVolumeGroupInfo> groupInfos = zone.getCurrentVolumeGroupInfos(); 127 128 List<AudioAttributes> attributesHoldingFocus = 129 CarDuckingUtils.getAudioAttributesHoldingFocus(focusHolders); 130 131 OemCarAudioVolumeRequest request = new OemCarAudioVolumeRequest.Builder(zoneId) 132 .setActivePlaybackAttributes(attributesHoldingFocus) 133 .setCarVolumeGroupInfos(groupInfos).build(); 134 135 List<AudioAttributes> audioAttributesToDuck = evaluateAttributesToDuck(request); 136 137 return CarDuckingUtils.generateDuckingInfo(oldDuckingInfo, audioAttributesToDuck, 138 attributesHoldingFocus, zone); 139 } 140 evaluateAttributesToDuck(OemCarAudioVolumeRequest requestInfo)141 private List<AudioAttributes> evaluateAttributesToDuck(OemCarAudioVolumeRequest requestInfo) { 142 return isOemDuckingServiceAvailable() ? evaluateAttributesToDuckExternally(requestInfo) : 143 evaluateAttributesToDuckInternally(requestInfo); 144 } 145 evaluateAttributesToDuckExternally( OemCarAudioVolumeRequest requestInfo)146 private List<AudioAttributes> evaluateAttributesToDuckExternally( 147 OemCarAudioVolumeRequest requestInfo) { 148 return CarLocalServices.getService(CarOemProxyService.class).getCarOemAudioDuckingService() 149 .evaluateAttributesToDuck(requestInfo); 150 } 151 evaluateAttributesToDuckInternally( OemCarAudioVolumeRequest requestInfo)152 private List<AudioAttributes> evaluateAttributesToDuckInternally( 153 OemCarAudioVolumeRequest requestInfo) { 154 return CarAudioContext.evaluateAudioAttributesToDuck( 155 requestInfo.getActivePlaybackAttributes()); 156 } 157 isOemDuckingServiceAvailable()158 private boolean isOemDuckingServiceAvailable() { 159 CarOemProxyService carService = CarLocalServices.getService(CarOemProxyService.class); 160 161 return carService != null 162 && carService.isOemServiceEnabled() && carService.isOemServiceReady() 163 && carService.getCarOemAudioDuckingService() != null; 164 } 165 } 166