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.CarAudioContext.ALARM; 20 import static com.android.car.audio.CarAudioContext.ANNOUNCEMENT; 21 import static com.android.car.audio.CarAudioContext.CALL; 22 import static com.android.car.audio.CarAudioContext.CALL_RING; 23 import static com.android.car.audio.CarAudioContext.EMERGENCY; 24 import static com.android.car.audio.CarAudioContext.INVALID; 25 import static com.android.car.audio.CarAudioContext.MUSIC; 26 import static com.android.car.audio.CarAudioContext.NAVIGATION; 27 import static com.android.car.audio.CarAudioContext.NOTIFICATION; 28 import static com.android.car.audio.CarAudioContext.SAFETY; 29 import static com.android.car.audio.CarAudioContext.SYSTEM_SOUND; 30 import static com.android.car.audio.CarAudioContext.VEHICLE_STATUS; 31 import static com.android.car.audio.CarAudioContext.VOICE_COMMAND; 32 33 import android.media.AudioFocusInfo; 34 import android.util.SparseArray; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 38 import java.util.ArrayList; 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Set; 42 43 final class CarDuckingUtils { 44 @VisibleForTesting 45 static final SparseArray<int[]> sContextsToDuck = new SparseArray<>(); 46 47 static { 48 // INVALID ducks nothing sContextsToDuck.append(INVALID, new int[0])49 sContextsToDuck.append(INVALID, new int[0]); 50 // MUSIC ducks nothing sContextsToDuck.append(MUSIC, new int[0])51 sContextsToDuck.append(MUSIC, new int[0]); sContextsToDuck.append(NAVIGATION, new int[]{ MUSIC, CALL_RING, CALL, ALARM, NOTIFICATION, SYSTEM_SOUND, VEHICLE_STATUS, ANNOUNCEMENT })52 sContextsToDuck.append(NAVIGATION, new int[]{ 53 MUSIC, 54 CALL_RING, 55 CALL, 56 ALARM, 57 NOTIFICATION, 58 SYSTEM_SOUND, 59 VEHICLE_STATUS, 60 ANNOUNCEMENT 61 }); sContextsToDuck.append(VOICE_COMMAND, new int[]{ CALL_RING })62 sContextsToDuck.append(VOICE_COMMAND, new int[]{ 63 CALL_RING 64 }); sContextsToDuck.append(CALL_RING, new int[0])65 sContextsToDuck.append(CALL_RING, new int[0]); sContextsToDuck.append(CALL, new int[]{ CALL_RING, ALARM, NOTIFICATION, VEHICLE_STATUS })66 sContextsToDuck.append(CALL, new int[]{ 67 CALL_RING, 68 ALARM, 69 NOTIFICATION, 70 VEHICLE_STATUS 71 }); sContextsToDuck.append(ALARM, new int[]{ MUSIC })72 sContextsToDuck.append(ALARM, new int[]{ 73 MUSIC 74 }); sContextsToDuck.append(NOTIFICATION, new int[]{ MUSIC, ALARM, ANNOUNCEMENT })75 sContextsToDuck.append(NOTIFICATION, new int[]{ 76 MUSIC, 77 ALARM, 78 ANNOUNCEMENT 79 }); sContextsToDuck.append(SYSTEM_SOUND, new int[]{ MUSIC, ALARM, ANNOUNCEMENT })80 sContextsToDuck.append(SYSTEM_SOUND, new int[]{ 81 MUSIC, 82 ALARM, 83 ANNOUNCEMENT 84 }); sContextsToDuck.append(EMERGENCY, new int[]{ CALL })85 sContextsToDuck.append(EMERGENCY, new int[]{ 86 CALL 87 }); sContextsToDuck.append(SAFETY, new int[]{ MUSIC, NAVIGATION, VOICE_COMMAND, CALL_RING, CALL, ALARM, NOTIFICATION, SYSTEM_SOUND, VEHICLE_STATUS, ANNOUNCEMENT })88 sContextsToDuck.append(SAFETY, new int[]{ 89 MUSIC, 90 NAVIGATION, 91 VOICE_COMMAND, 92 CALL_RING, 93 CALL, 94 ALARM, 95 NOTIFICATION, 96 SYSTEM_SOUND, 97 VEHICLE_STATUS, 98 ANNOUNCEMENT 99 }); sContextsToDuck.append(VEHICLE_STATUS, new int[]{ MUSIC, CALL_RING, ANNOUNCEMENT })100 sContextsToDuck.append(VEHICLE_STATUS, new int[]{ 101 MUSIC, 102 CALL_RING, 103 ANNOUNCEMENT 104 }); 105 // ANNOUNCEMENT ducks nothing sContextsToDuck.append(ANNOUNCEMENT, new int[0])106 sContextsToDuck.append(ANNOUNCEMENT, new int[0]); 107 } 108 CarDuckingUtils()109 private CarDuckingUtils() { 110 } 111 getUsagesHoldingFocus(List<AudioFocusInfo> focusHolders)112 static int[] getUsagesHoldingFocus(List<AudioFocusInfo> focusHolders) { 113 Set<Integer> uniqueUsages = new HashSet<>(); 114 for (AudioFocusInfo focusInfo : focusHolders) { 115 uniqueUsages.add(focusInfo.getAttributes().getSystemUsage()); 116 } 117 118 int index = 0; 119 int[] usagesHoldingFocus = new int[uniqueUsages.size()]; 120 for (int usage : uniqueUsages) { 121 usagesHoldingFocus[index] = usage; 122 index++; 123 } 124 return usagesHoldingFocus; 125 } 126 getAddressesToDuck(int[] usages, CarAudioZone zone)127 static List<String> getAddressesToDuck(int[] usages, CarAudioZone zone) { 128 Set<Integer> uniqueContexts = CarAudioContext.getUniqueContextsForUsages(usages); 129 uniqueContexts.remove(INVALID); 130 Set<Integer> contextsToDuck = getContextsToDuck(uniqueContexts); 131 Set<String> addressesToDuck = getAddressesForContexts(contextsToDuck, zone); 132 133 Set<Integer> unduckedContexts = getUnduckedContexts(uniqueContexts, contextsToDuck); 134 Set<String> unduckedAddresses = getAddressesForContexts(unduckedContexts, zone); 135 136 // We should not duck any device that's associated with an unducked context holding focus. 137 addressesToDuck.removeAll(unduckedAddresses); 138 return new ArrayList<>(addressesToDuck); 139 } 140 getAddressesToUnduck(List<String> addressesToDuck, List<String> oldAddressesToDuck)141 static List<String> getAddressesToUnduck(List<String> addressesToDuck, 142 List<String> oldAddressesToDuck) { 143 List<String> addressesToUnduck = new ArrayList<>(oldAddressesToDuck); 144 addressesToUnduck.removeAll(addressesToDuck); 145 return addressesToUnduck; 146 } 147 getUnduckedContexts(Set<Integer> contexts, Set<Integer> duckedContexts)148 private static Set<Integer> getUnduckedContexts(Set<Integer> contexts, 149 Set<Integer> duckedContexts) { 150 Set<Integer> unduckedContexts = new HashSet<>(contexts); 151 unduckedContexts.removeAll(duckedContexts); 152 return unduckedContexts; 153 } 154 getAddressesForContexts(Set<Integer> contexts, CarAudioZone zone)155 private static Set<String> getAddressesForContexts(Set<Integer> contexts, CarAudioZone zone) { 156 Set<String> addresses = new HashSet<>(); 157 for (Integer context : contexts) { 158 addresses.add(zone.getAddressForContext(context)); 159 } 160 return addresses; 161 } 162 getContextsToDuck(Set<Integer> contexts)163 private static Set<Integer> getContextsToDuck(Set<Integer> contexts) { 164 Set<Integer> contextsToDuck = new HashSet<>(); 165 166 for (Integer context : contexts) { 167 int[] duckedContexts = sContextsToDuck.get(context); 168 for (int i = 0; i < duckedContexts.length; i++) { 169 contextsToDuck.add(duckedContexts[i]); 170 } 171 } 172 173 // Reduce contextsToDuck down to subset of contexts currently holding focus 174 contextsToDuck.retainAll(contexts); 175 return contextsToDuck; 176 } 177 } 178