blob: eff016855f4197d36640d517f676c7a9e8c8e476 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.audio.hal;
import static android.media.AudioManager.AUDIOFOCUS_LOSS;
import static android.media.AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
import static android.media.AudioManager.AUDIOFOCUS_REQUEST_FAILED;
import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
import android.car.media.CarAudioManager;
import android.hardware.automotive.audiocontrol.V2_0.IFocusListener;
import android.media.AudioAttributes;
import android.media.AudioAttributes.AttributeUsage;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
import androidx.annotation.NonNull;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
import java.util.Objects;
/**
* Manages focus requests from the HAL on a per-zone per-usage basis
*/
public final class HalAudioFocus extends IFocusListener.Stub {
private static final String TAG = HalAudioFocus.class.getSimpleName();
private final AudioManager mAudioManager;
private final AudioControlWrapper mAudioControlWrapper;
private final Object mLock = new Object();
// Map of Maps. Top level keys are ZoneIds. Second level keys are usages.
// Values are HalAudioFocusRequests
@GuardedBy("mImplLock")
private final SparseArray<SparseArray<HalAudioFocusRequest>> mHalFocusRequestsByZoneAndUsage;
public HalAudioFocus(@NonNull AudioManager audioManager,
@NonNull AudioControlWrapper audioControlWrapper,
@NonNull int[] audioZoneIds) {
mAudioManager = Objects.requireNonNull(audioManager);
mAudioControlWrapper = Objects.requireNonNull(audioControlWrapper);
Objects.requireNonNull(audioZoneIds);
mHalFocusRequestsByZoneAndUsage = new SparseArray<>(audioZoneIds.length);
for (int zoneId : audioZoneIds) {
mHalFocusRequestsByZoneAndUsage.append(zoneId, new SparseArray<>());
}
}
/**
* Registers {@code IFocusListener} on {@code AudioControlWrapper} to receive HAL audio focus
* request and abandon calls.
*/
public void registerFocusListener() {
mAudioControlWrapper.registerFocusListener(this);
}
/**
* Unregisters {@code IFocusListener} from {@code AudioControlWrapper}.
*/
public void unregisterFocusListener() {
mAudioControlWrapper.unregisterFocusListener();
}
@Override
public void requestAudioFocus(@AttributeUsage int usage, int zoneId, int focusGain) {
Preconditions.checkArgument(mHalFocusRequestsByZoneAndUsage.contains(zoneId),
"Invalid zoneId %d provided in requestAudioFocus", zoneId);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Requesting focus gain " + focusGain + " with usage "
+ AudioAttributes.usageToString(usage) + " and zoneId " + zoneId);
}
synchronized (mLock) {
HalAudioFocusRequest currentRequest = mHalFocusRequestsByZoneAndUsage.get(zoneId).get(
usage);
if (currentRequest != null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "A request already exists for zoneId " + zoneId + " and usage "
+ usage);
}
mAudioControlWrapper.onAudioFocusChange(usage, zoneId, currentRequest.mFocusStatus);
} else {
makeAudioFocusRequestLocked(usage, zoneId, focusGain);
}
}
}
@Override
public void abandonAudioFocus(int usage, int zoneId) throws RemoteException {
Preconditions.checkArgument(mHalFocusRequestsByZoneAndUsage.contains(zoneId),
"Invalid zoneId %d provided in abandonAudioFocus", zoneId);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Abandoning focus with usage " + AudioAttributes.usageToString(usage)
+ " for zoneId " + zoneId);
}
synchronized (mLock) {
abandonAudioFocusLocked(usage, zoneId);
}
}
/**
* Clear out all existing focus requests. Called when HAL dies.
*/
public void reset() {
Log.d(TAG, "Resetting HAL Audio Focus requests");
synchronized (mLock) {
for (int i = 0; i < mHalFocusRequestsByZoneAndUsage.size(); i++) {
int zoneId = mHalFocusRequestsByZoneAndUsage.keyAt(i);
SparseArray<HalAudioFocusRequest> requestsByUsage =
mHalFocusRequestsByZoneAndUsage.valueAt(i);
int usageCount = requestsByUsage.size();
for (int j = 0; j < usageCount; j++) {
int usage = requestsByUsage.keyAt(j);
abandonAudioFocusLocked(usage, zoneId);
}
}
}
}
/**
* dumps the current state of the HalAudioFocus
*
* @param indent indent to append to each new line
* @param writer stream to write current state
*/
public void dump(String indent, PrintWriter writer) {
writer.printf("%s*HalAudioFocus*\n", indent);
writer.printf("%s\tCurrent focus requests:\n", indent);
for (int i = 0; i < mHalFocusRequestsByZoneAndUsage.size(); i++) {
int zoneId = mHalFocusRequestsByZoneAndUsage.keyAt(i);
writer.printf("%s\t\tZone %s:\n", indent, zoneId);
SparseArray<HalAudioFocusRequest> requestsByUsage =
mHalFocusRequestsByZoneAndUsage.valueAt(i);
for (int j = 0; j < requestsByUsage.size(); j++) {
int usage = requestsByUsage.keyAt(j);
HalAudioFocusRequest request = requestsByUsage.valueAt(j);
writer.printf("%s\t\t\t%s - focusGain: %s\n", indent,
AudioAttributes.usageToString(usage), request.mFocusStatus);
}
}
}
private void abandonAudioFocusLocked(int usage, int zoneId) {
HalAudioFocusRequest currentRequest = mHalFocusRequestsByZoneAndUsage.get(zoneId)
.removeReturnOld(usage);
if (currentRequest == null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "No focus to abandon for usage " + AudioAttributes.usageToString(usage)
+ " and zoneId " + zoneId);
}
return;
}
int result = mAudioManager.abandonAudioFocusRequest(currentRequest.mAudioFocusRequest);
if (result == AUDIOFOCUS_REQUEST_GRANTED) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Abandoned focus for usage " + AudioAttributes.usageToString(usage)
+ "and zoneId " + zoneId);
}
mAudioControlWrapper.onAudioFocusChange(usage, zoneId, AUDIOFOCUS_LOSS);
} else {
Log.w(TAG,
"Failed to abandon focus for usage " + AudioAttributes.usageToString(usage)
+ " and zoneId " + zoneId);
}
}
private AudioAttributes generateAudioAttributes(int usage, int zoneId) {
AudioAttributes.Builder builder = new AudioAttributes.Builder();
Bundle bundle = new Bundle();
bundle.putInt(CarAudioManager.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID, zoneId);
builder.addBundle(bundle);
if (AudioAttributes.isSystemUsage(usage)) {
builder.setSystemUsage(usage);
} else {
builder.setUsage(usage);
}
return builder.build();
}
private AudioFocusRequest generateFocusRequestLocked(int usage, int zoneId, int focusGain) {
AudioAttributes attributes = generateAudioAttributes(usage, zoneId);
return new AudioFocusRequest.Builder(focusGain)
.setAudioAttributes(attributes)
.setOnAudioFocusChangeListener((int focusChange) -> {
onAudioFocusChange(usage, zoneId, focusChange);
})
.build();
}
private void onAudioFocusChange(int usage, int zoneId, int focusChange) {
synchronized (mLock) {
HalAudioFocusRequest currentRequest = mHalFocusRequestsByZoneAndUsage.get(zoneId).get(
usage);
if (currentRequest != null) {
if (focusChange == AUDIOFOCUS_LOSS) {
mHalFocusRequestsByZoneAndUsage.get(zoneId).remove(usage);
} else {
currentRequest.mFocusStatus = focusChange;
}
mAudioControlWrapper.onAudioFocusChange(usage, zoneId, focusChange);
}
}
}
private void makeAudioFocusRequestLocked(@AttributeUsage int usage, int zoneId, int focusGain) {
AudioFocusRequest audioFocusRequest = generateFocusRequestLocked(usage, zoneId, focusGain);
int requestResult = mAudioManager.requestAudioFocus(audioFocusRequest);
int resultingFocusGain = focusGain;
if (requestResult == AUDIOFOCUS_REQUEST_GRANTED) {
HalAudioFocusRequest halAudioFocusRequest = new HalAudioFocusRequest(audioFocusRequest,
focusGain);
mHalFocusRequestsByZoneAndUsage.get(zoneId).append(usage, halAudioFocusRequest);
} else if (requestResult == AUDIOFOCUS_REQUEST_FAILED) {
resultingFocusGain = AUDIOFOCUS_LOSS;
} else if (requestResult == AUDIOFOCUS_REQUEST_DELAYED) {
Log.w(TAG, "Delayed result for request with usage "
+ AudioAttributes.usageToString(usage) + ", zoneId " + zoneId
+ ", and focusGain " + focusGain);
resultingFocusGain = AUDIOFOCUS_LOSS;
}
mAudioControlWrapper.onAudioFocusChange(usage, zoneId, resultingFocusGain);
}
private final class HalAudioFocusRequest {
final AudioFocusRequest mAudioFocusRequest;
int mFocusStatus;
HalAudioFocusRequest(AudioFocusRequest audioFocusRequest, int focusStatus) {
mAudioFocusRequest = audioFocusRequest;
mFocusStatus = focusStatus;
}
}
}