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