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