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