1 /* 2 * Copyright (C) 2020 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.hal; 18 19 import static android.car.builtin.media.AudioManagerHelper.usageToString; 20 import static android.media.AudioManager.AUDIOFOCUS_LOSS; 21 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_DELAYED; 22 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_FAILED; 23 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 24 25 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 26 27 import android.annotation.NonNull; 28 import android.car.builtin.util.Slogf; 29 import android.car.media.CarAudioManager; 30 import android.media.AudioAttributes; 31 import android.media.AudioFocusRequest; 32 import android.media.AudioManager; 33 import android.os.Bundle; 34 import android.util.ArrayMap; 35 import android.util.ArraySet; 36 import android.util.Log; 37 import android.util.SparseArray; 38 39 import com.android.car.CarLog; 40 import com.android.car.audio.CarAudioContext; 41 import com.android.car.audio.CarAudioContext.AudioAttributesWrapper; 42 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 43 import com.android.car.internal.annotation.AttributeUsage; 44 import com.android.car.internal.util.IndentingPrintWriter; 45 import com.android.internal.annotations.GuardedBy; 46 import com.android.internal.util.Preconditions; 47 48 import java.util.ArrayList; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Objects; 52 import java.util.Set; 53 54 /** 55 * Manages focus requests from the HAL on a per-zone per-usage basis 56 */ 57 public final class HalAudioFocus implements HalFocusListener { 58 private static final String TAG = CarLog.tagFor(HalAudioFocus.class); 59 60 private final AudioManager mAudioManager; 61 private final AudioControlWrapper mAudioControlWrapper; 62 63 private final Object mLock = new Object(); 64 65 // Map of Maps. Top level keys are ZoneIds. Second level keys are audio attribute wrapper. 66 // Values are HalAudioFocusRequests 67 @GuardedBy("mLock") 68 private final SparseArray<Map<AudioAttributesWrapper, HalAudioFocusRequest>> 69 mHalFocusRequestsByZoneAndUsage; 70 HalAudioFocus(@onNull AudioManager audioManager, @NonNull AudioControlWrapper audioControlWrapper, @NonNull int[] audioZoneIds)71 public HalAudioFocus(@NonNull AudioManager audioManager, 72 @NonNull AudioControlWrapper audioControlWrapper, 73 @NonNull int[] audioZoneIds) { 74 mAudioManager = Objects.requireNonNull(audioManager); 75 mAudioControlWrapper = Objects.requireNonNull(audioControlWrapper); 76 Objects.requireNonNull(audioZoneIds); 77 78 mHalFocusRequestsByZoneAndUsage = new SparseArray<>(audioZoneIds.length); 79 for (int index = 0; index < audioZoneIds.length; index++) { 80 mHalFocusRequestsByZoneAndUsage.put(audioZoneIds[index], new ArrayMap<>()); 81 } 82 } 83 84 /** 85 * Registers {@code IFocusListener} on {@code AudioControlWrapper} to receive HAL audio focus 86 * request and abandon calls. 87 */ registerFocusListener()88 public void registerFocusListener() { 89 mAudioControlWrapper.registerFocusListener(this); 90 } 91 92 /** 93 * Unregisters {@code IFocusListener} from {@code AudioControlWrapper}. 94 */ unregisterFocusListener()95 public void unregisterFocusListener() { 96 mAudioControlWrapper.unregisterFocusListener(); 97 } 98 99 /** 100 * See {@link HalFocusListener#requestAudioFocus(int, int, int)} 101 */ requestAudioFocus(@ttributeUsage int usage, int zoneId, int focusGain)102 public void requestAudioFocus(@AttributeUsage int usage, int zoneId, int focusGain) { 103 synchronized (mLock) { 104 Preconditions.checkArgument(mHalFocusRequestsByZoneAndUsage.contains(zoneId), 105 "Invalid zoneId %d provided in requestAudioFocus", zoneId); 106 if (Slogf.isLoggable(TAG, Log.DEBUG)) { 107 Slogf.d(TAG, "Requesting focus gain " + focusGain + " with usage " 108 + usageToString(usage) + " and zoneId " + zoneId); 109 } 110 AudioAttributesWrapper audioAttributesWrapper = 111 CarAudioContext.getAudioAttributeWrapperFromUsage(usage); 112 HalAudioFocusRequest currentRequest = 113 mHalFocusRequestsByZoneAndUsage.get(zoneId).get(audioAttributesWrapper); 114 if (currentRequest != null) { 115 if (Slogf.isLoggable(TAG, Log.DEBUG)) { 116 Slogf.d(TAG, "A request already exists for zoneId " + zoneId + " and usage " 117 + usage); 118 } 119 mAudioControlWrapper.onAudioFocusChange(usage, zoneId, currentRequest.mFocusStatus); 120 } else { 121 makeAudioFocusRequestLocked(audioAttributesWrapper, zoneId, focusGain); 122 } 123 } 124 } 125 126 /** 127 * See {@link HalFocusListener#abandonAudioFocus(int, int)} 128 */ abandonAudioFocus(@ttributeUsage int usage, int zoneId)129 public void abandonAudioFocus(@AttributeUsage int usage, int zoneId) { 130 synchronized (mLock) { 131 Preconditions.checkArgument(mHalFocusRequestsByZoneAndUsage.contains(zoneId), 132 "Invalid zoneId %d provided in abandonAudioFocus", zoneId); 133 if (Slogf.isLoggable(TAG, Log.DEBUG)) { 134 Slogf.d(TAG, "Abandoning focus with usage " + usageToString(usage) 135 + " for zoneId " + zoneId); 136 } 137 abandonAudioFocusLocked(CarAudioContext.getAudioAttributeWrapperFromUsage(usage), 138 zoneId); 139 } 140 } 141 142 /** 143 * Clear out all existing focus requests. Called when HAL dies. 144 */ reset()145 public void reset() { 146 Slogf.d(TAG, "Resetting HAL Audio Focus requests"); 147 synchronized (mLock) { 148 for (int i = 0; i < mHalFocusRequestsByZoneAndUsage.size(); i++) { 149 int zoneId = mHalFocusRequestsByZoneAndUsage.keyAt(i); 150 Map<AudioAttributesWrapper, HalAudioFocusRequest> 151 requestsByAttributes = mHalFocusRequestsByZoneAndUsage.valueAt(i); 152 Set<AudioAttributesWrapper> wrapperSet = 153 new ArraySet<>(requestsByAttributes.keySet()); 154 for (AudioAttributesWrapper wrapper : wrapperSet) { 155 abandonAudioFocusLocked(wrapper, zoneId); 156 } 157 } 158 } 159 } 160 161 /** 162 * Returns the currently active {@link AudioAttribute}'s for an audio zone 163 */ getActiveAudioAttributesForZone(int audioZoneId)164 public List<AudioAttributes> getActiveAudioAttributesForZone(int audioZoneId) { 165 synchronized (mLock) { 166 Map<AudioAttributesWrapper, HalAudioFocusRequest> halFocusRequestsForZone = 167 mHalFocusRequestsByZoneAndUsage.get(audioZoneId); 168 List<AudioAttributes> activeAudioAttributes = 169 new ArrayList<>(halFocusRequestsForZone.size()); 170 171 for (AudioAttributesWrapper wrapper : halFocusRequestsForZone.keySet()) { 172 activeAudioAttributes.add(wrapper.getAudioAttributes()); 173 } 174 175 return activeAudioAttributes; 176 } 177 } 178 179 /** 180 * dumps the current state of the HalAudioFocus 181 * 182 * @param writer stream to write current state 183 */ 184 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)185 public void dump(IndentingPrintWriter writer) { 186 writer.println("*HalAudioFocus*"); 187 188 writer.increaseIndent(); 189 writer.println("Current focus requests:"); 190 writer.increaseIndent(); 191 synchronized (mLock) { 192 for (int i = 0; i < mHalFocusRequestsByZoneAndUsage.size(); i++) { 193 int zoneId = mHalFocusRequestsByZoneAndUsage.keyAt(i); 194 writer.printf("Zone %s:\n", zoneId); 195 writer.increaseIndent(); 196 197 Map<AudioAttributesWrapper, HalAudioFocusRequest> requestsByAttributes = 198 mHalFocusRequestsByZoneAndUsage.valueAt(i); 199 for (HalAudioFocusRequest request : requestsByAttributes.values()) { 200 writer.printf("%s\n", request); 201 } 202 writer.decreaseIndent(); 203 } 204 } 205 writer.decreaseIndent(); 206 writer.decreaseIndent(); 207 } 208 209 @GuardedBy("mLock") abandonAudioFocusLocked(AudioAttributesWrapper audioAttributesWrapper, int zoneId)210 private void abandonAudioFocusLocked(AudioAttributesWrapper audioAttributesWrapper, 211 int zoneId) { 212 Map<AudioAttributesWrapper, HalAudioFocusRequest> halAudioFocusRequests = 213 mHalFocusRequestsByZoneAndUsage.get(zoneId); 214 HalAudioFocusRequest currentRequest = halAudioFocusRequests.get(audioAttributesWrapper); 215 216 if (currentRequest == null) { 217 if (Slogf.isLoggable(TAG, Log.DEBUG)) { 218 Slogf.d(TAG, "No focus to abandon for audio attributes " + audioAttributesWrapper 219 + " and zoneId " + zoneId); 220 } 221 return; 222 } else { 223 // remove it from map 224 halAudioFocusRequests.remove(audioAttributesWrapper); 225 } 226 227 int result = mAudioManager.abandonAudioFocusRequest(currentRequest.mAudioFocusRequest); 228 if (result == AUDIOFOCUS_REQUEST_GRANTED) { 229 if (Slogf.isLoggable(TAG, Log.DEBUG)) { 230 Slogf.d(TAG, "Abandoned focus for audio attributes " + audioAttributesWrapper 231 + "and zoneId " + zoneId); 232 } 233 mAudioControlWrapper.onAudioFocusChange(audioAttributesWrapper.getAudioAttributes() 234 .getSystemUsage(), zoneId, AUDIOFOCUS_LOSS); 235 } else { 236 Slogf.w(TAG, "Failed to abandon focus for audio attributes " + audioAttributesWrapper 237 + " and zoneId " + zoneId); 238 } 239 } 240 generateAudioAttributes( AudioAttributesWrapper audioAttributesWrapper, int zoneId)241 private AudioAttributes generateAudioAttributes( 242 AudioAttributesWrapper audioAttributesWrapper, int zoneId) { 243 AudioAttributes.Builder builder = 244 new AudioAttributes.Builder(audioAttributesWrapper.getAudioAttributes()); 245 Bundle bundle = new Bundle(); 246 bundle.putInt(CarAudioManager.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID, zoneId); 247 builder.addBundle(bundle); 248 249 return builder.build(); 250 } 251 252 @GuardedBy("mLock") generateFocusRequestLocked( AudioAttributesWrapper audioAttributesWrapper, int zoneId, int focusGain)253 private AudioFocusRequest generateFocusRequestLocked( 254 AudioAttributesWrapper audioAttributesWrapper, 255 int zoneId, int focusGain) { 256 AudioAttributes attributes = generateAudioAttributes(audioAttributesWrapper, zoneId); 257 return new AudioFocusRequest.Builder(focusGain) 258 .setAudioAttributes(attributes) 259 .setOnAudioFocusChangeListener((int focusChange) -> { 260 onAudioFocusChange(attributes.getSystemUsage(), zoneId, focusChange); 261 }) 262 .build(); 263 } 264 onAudioFocusChange(int usage, int zoneId, int focusChange)265 private void onAudioFocusChange(int usage, int zoneId, int focusChange) { 266 AudioAttributesWrapper wrapper = CarAudioContext.getAudioAttributeWrapperFromUsage(usage); 267 synchronized (mLock) { 268 HalAudioFocusRequest currentRequest = 269 mHalFocusRequestsByZoneAndUsage.get(zoneId).get(wrapper); 270 if (currentRequest != null) { 271 if (focusChange == AUDIOFOCUS_LOSS) { 272 mHalFocusRequestsByZoneAndUsage.get(zoneId).remove(wrapper); 273 } else { 274 currentRequest.mFocusStatus = focusChange; 275 } 276 mAudioControlWrapper.onAudioFocusChange(usage, zoneId, focusChange); 277 } 278 279 } 280 } 281 282 @GuardedBy("mLock") makeAudioFocusRequestLocked( AudioAttributesWrapper audioAttributesWrapper, int zoneId, int focusGain)283 private void makeAudioFocusRequestLocked( 284 AudioAttributesWrapper audioAttributesWrapper, 285 int zoneId, int focusGain) { 286 AudioFocusRequest audioFocusRequest = 287 generateFocusRequestLocked(audioAttributesWrapper, zoneId, focusGain); 288 289 int requestResult = mAudioManager.requestAudioFocus(audioFocusRequest); 290 291 int resultingFocusGain = focusGain; 292 293 if (requestResult == AUDIOFOCUS_REQUEST_GRANTED) { 294 HalAudioFocusRequest halAudioFocusRequest = 295 new HalAudioFocusRequest(audioFocusRequest, focusGain); 296 mHalFocusRequestsByZoneAndUsage.get(zoneId) 297 .put(audioAttributesWrapper, halAudioFocusRequest); 298 } else if (requestResult == AUDIOFOCUS_REQUEST_FAILED) { 299 resultingFocusGain = AUDIOFOCUS_LOSS; 300 } else if (requestResult == AUDIOFOCUS_REQUEST_DELAYED) { 301 Slogf.w(TAG, "Delayed result for request with audio attributes " 302 + audioAttributesWrapper + ", zoneId " + zoneId 303 + ", and focusGain " + focusGain); 304 resultingFocusGain = AUDIOFOCUS_LOSS; 305 } 306 307 mAudioControlWrapper.onAudioFocusChange( 308 audioAttributesWrapper.getAudioAttributes().getSystemUsage(), 309 zoneId, resultingFocusGain); 310 } 311 312 private final class HalAudioFocusRequest { 313 final AudioFocusRequest mAudioFocusRequest; 314 315 int mFocusStatus; 316 HalAudioFocusRequest(AudioFocusRequest audioFocusRequest, int focusStatus)317 HalAudioFocusRequest(AudioFocusRequest audioFocusRequest, int focusStatus) { 318 mAudioFocusRequest = audioFocusRequest; 319 mFocusStatus = focusStatus; 320 } 321 322 @Override toString()323 public String toString() { 324 return new StringBuilder() 325 .append("Request: ") 326 .append("[Audio attributes: ") 327 .append(mAudioFocusRequest.getAudioAttributes()) 328 .append(", Focus request: ") 329 .append(mAudioFocusRequest.getFocusGain()) 330 .append("]") 331 .append(", Status: ") 332 .append(mFocusStatus) 333 .toString(); 334 } 335 } 336 } 337