blob: 5019fb467b572316ce8cfdcdd45d641ed3005e18 [file] [log] [blame]
/*
* Copyright (C) 2015 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;
import android.car.media.CarAudioManager;
import android.car.media.ICarAudio;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
import android.media.AudioManager;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import com.android.car.hal.AudioHalService;
import com.android.car.hal.AudioHalService.AudioHalListener;
import com.android.car.hal.VehicleHal;
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
import java.util.LinkedList;
public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, AudioHalListener {
private static final long FOCUS_RESPONSE_WAIT_TIMEOUT_MS = 1000;
private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS";
private static final boolean DBG = true;
private final AudioHalService mAudioHal;
private final Context mContext;
private final HandlerThread mFocusHandlerThread;
private final CarAudioFocusChangeHandler mFocusHandler;
private final CarAudioVolumeHandler mVolumeHandler;
private final SystemFocusListener mSystemFocusListener;
private AudioPolicy mAudioPolicy;
private final Object mLock = new Object();
@GuardedBy("mLock")
private FocusState mCurrentFocusState = FocusState.STATE_LOSS;
/** Focus state received, but not handled yet. Once handled, this will be set to null. */
@GuardedBy("mLock")
private FocusState mFocusReceived = null;
@GuardedBy("mLock")
private FocusRequest mLastFocusRequestToCar = null;
@GuardedBy("mLock")
private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>();
@GuardedBy("mLock")
private AudioFocusInfo mTopFocusInfo = null;
/** previous top which may be in ducking state */
@GuardedBy("mLock")
private AudioFocusInfo mSecondFocusInfo = null;
private AudioRoutingPolicy mAudioRoutingPolicy;
private final AudioManager mAudioManager;
private final BottomAudioFocusListener mBottomAudioFocusHandler =
new BottomAudioFocusListener();
private final CarProxyAndroidFocusListener mCarProxyAudioFocusHandler =
new CarProxyAndroidFocusListener();
@GuardedBy("mLock")
private int mBottomFocusState;
@GuardedBy("mLock")
private boolean mRadioActive = false;
@GuardedBy("mLock")
private boolean mCallActive = false;
@GuardedBy("mLock")
private int mCurrentAudioContexts = 0;
private final AudioAttributes mAttributeBottom =
CarAudioAttributesUtil.getAudioAttributesForCarUsage(
CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM);
private final AudioAttributes mAttributeCarExternal =
CarAudioAttributesUtil.getAudioAttributesForCarUsage(
CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY);
public CarAudioService(Context context) {
mAudioHal = VehicleHal.getInstance().getAudioHal();
mContext = context;
mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
mSystemFocusListener = new SystemFocusListener();
mFocusHandlerThread.start();
mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper());
mVolumeHandler = new CarAudioVolumeHandler(Looper.getMainLooper());
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
}
@Override
public AudioAttributes getAudioAttributesForCarUsage(int carUsage) {
return CarAudioAttributesUtil.getAudioAttributesForCarUsage(carUsage);
}
@Override
public void init() {
AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
builder.setLooper(Looper.getMainLooper());
boolean isFocusSuported = mAudioHal.isFocusSupported();
if (isFocusSuported) {
builder.setAudioPolicyFocusListener(mSystemFocusListener);
}
mAudioPolicy = builder.build();
if (isFocusSuported) {
FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState());
int r = mAudioManager.requestAudioFocus(mBottomAudioFocusHandler, mAttributeBottom,
AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
synchronized (mLock) {
if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN;
} else {
mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
}
mCurrentFocusState = currentState;
mCurrentAudioContexts = 0;
}
}
int r = mAudioManager.registerAudioPolicy(mAudioPolicy);
if (r != 0) {
throw new RuntimeException("registerAudioPolicy failed " + r);
}
mAudioHal.setListener(this);
int audioHwVariant = mAudioHal.getHwVariant();
mAudioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
mAudioHal.setAudioRoutingPolicy(mAudioRoutingPolicy);
//TODO set routing policy with new AudioPolicy API. This will control which logical stream
// goes to which physical stream.
}
@Override
public void release() {
mAudioManager.unregisterAudioPolicyAsync(mAudioPolicy);
mAudioManager.abandonAudioFocus(mBottomAudioFocusHandler);
mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
mFocusHandler.cancelAll();
synchronized (mLock) {
mCurrentFocusState = FocusState.STATE_LOSS;
mLastFocusRequestToCar = null;
mTopFocusInfo = null;
mPendingFocusChanges.clear();
mRadioActive = false;
}
}
@Override
public void dump(PrintWriter writer) {
writer.println("*CarAudioService*");
writer.println(" mCurrentFocusState:" + mCurrentFocusState +
" mLastFocusRequestToCar:" + mLastFocusRequestToCar);
writer.println(" mCurrentAudioContexts:0x" + Integer.toHexString(mCurrentAudioContexts));
writer.println(" mCallActive:" + mCallActive + " mRadioActive:" + mRadioActive);
mAudioRoutingPolicy.dump(writer);
}
@Override
public void onFocusChange(int focusState, int streams, int externalFocus) {
synchronized (mLock) {
mFocusReceived = FocusState.create(focusState, streams, externalFocus);
// wake up thread waiting for focus response.
mLock.notifyAll();
}
mFocusHandler.handleFocusChange();
}
@Override
public void onVolumeChange(int streamNumber, int volume, int volumeState) {
mVolumeHandler.handleVolumeChange(new VolumeStateChangeEvent(streamNumber, volume,
volumeState));
}
@Override
public void onVolumeLimitChange(int streamNumber, int volume) {
//TODO
}
@Override
public void onStreamStatusChange(int state, int streamNumber) {
mFocusHandler.handleStreamStateChange(state, streamNumber);
}
private void doHandleCarFocusChange() {
int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID;
FocusState currentState;
AudioFocusInfo topInfo;
synchronized (mLock) {
if (mFocusReceived == null) {
// already handled
return;
}
if (mFocusReceived.equals(mCurrentFocusState)) {
// no change
mFocusReceived = null;
return;
}
if (DBG) {
Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived);
}
topInfo = mTopFocusInfo;
if (!mFocusReceived.equals(mCurrentFocusState.focusState)) {
newFocusState = mFocusReceived.focusState;
}
mCurrentFocusState = mFocusReceived;
currentState = mFocusReceived;
mFocusReceived = null;
if (mLastFocusRequestToCar != null &&
(mLastFocusRequestToCar.focusRequest ==
AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN ||
mLastFocusRequestToCar.focusRequest ==
AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT ||
mLastFocusRequestToCar.focusRequest ==
AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) &&
(mCurrentFocusState.streams & mLastFocusRequestToCar.streams) !=
mLastFocusRequestToCar.streams) {
Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString(
mLastFocusRequestToCar.streams) + " got:0x" +
Integer.toHexString(mCurrentFocusState.streams));
// treat it as focus loss as requested streams are not there.
newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
}
mLastFocusRequestToCar = null;
if (mRadioActive &&
(mCurrentFocusState.externalFocus &
AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) {
// radio flag dropped
newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
mRadioActive = false;
}
}
switch (newFocusState) {
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
doHandleFocusGainFromCar(currentState, topInfo);
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
doHandleFocusGainTransientFromCar(currentState, topInfo);
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
doHandleFocusLossFromCar(currentState, topInfo);
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
doHandleFocusLossTransientFromCar(currentState);
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
doHandleFocusLossTransientCanDuckFromCar(currentState);
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
doHandleFocusLossTransientExclusiveFromCar(currentState);
break;
}
}
private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo) {
if (isFocusFromCarServiceBottom(topInfo)) {
Log.w(TAG_FOCUS, "focus gain from car:" + currentState +
" while bottom listener is top");
mFocusHandler.handleFocusReleaseRequest();
} else {
mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
}
}
private void doHandleFocusGainTransientFromCar(FocusState currentState,
AudioFocusInfo topInfo) {
if ((currentState.externalFocus &
(AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
} else {
if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) {
Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState +
" while bottom listener or car proxy is top");
mFocusHandler.handleFocusReleaseRequest();
}
}
}
private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) {
if (DBG) {
Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState +
" top:" + dumpAudioFocusInfo(topInfo));
}
boolean shouldRequestProxyFocus = false;
if ((currentState.externalFocus &
AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) {
shouldRequestProxyFocus = true;
}
if (isFocusFromCarProxy(topInfo)) {
if ((currentState.externalFocus &
(AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
// CarProxy in top, but no external focus: Drop it so that some other app
// may pick up focus.
mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
return;
}
} else if (!isFocusFromCarServiceBottom(topInfo)) {
shouldRequestProxyFocus = true;
}
if (shouldRequestProxyFocus) {
requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0);
}
}
private void doHandleFocusLossTransientFromCar(FocusState currentState) {
requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
}
private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) {
requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
}
private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) {
requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
AudioManager.AUDIOFOCUS_FLAG_LOCK);
}
private void requestCarProxyFocus(int androidFocus, int flags) {
mAudioManager.requestAudioFocus(mCarProxyAudioFocusHandler, mAttributeCarExternal,
androidFocus, flags, mAudioPolicy);
}
private void doHandleVolumeChange(VolumeStateChangeEvent event) {
//TODO
}
private void doHandleStreamStatusChange(int streamNumber, int state) {
//TODO
}
private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) {
if (info == null) {
return false;
}
AudioAttributes attrib = info.getAttributes();
if (info.getPackageName().equals(mContext.getPackageName()) &&
CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) {
return true;
}
return false;
}
private boolean isFocusFromCarProxy(AudioFocusInfo info) {
if (info == null) {
return false;
}
AudioAttributes attrib = info.getAttributes();
if (info.getPackageName().equals(mContext.getPackageName()) &&
attrib != null &&
CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY) {
return true;
}
return false;
}
private boolean isFocusFromRadio(AudioFocusInfo info) {
if (!mAudioHal.isRadioExternal()) {
// if radio is not external, no special handling of radio is necessary.
return false;
}
if (info == null) {
return false;
}
AudioAttributes attrib = info.getAttributes();
if (attrib != null &&
CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
CarAudioManager.CAR_AUDIO_USAGE_RADIO) {
return true;
}
return false;
}
/**
* Re-evaluate current focus state and send focus request to car if new focus was requested.
* @return true if focus change was requested to car.
*/
private boolean reevaluateCarAudioFocusLocked() {
if (mTopFocusInfo == null) {
// should not happen
Log.w(TAG_FOCUS, "reevaluateCarAudioFocusLocked, top focus info null");
return false;
}
if (mTopFocusInfo.getLossReceived() != 0) {
// top one got loss. This should not happen.
Log.e(TAG_FOCUS, "Top focus holder got loss " + dumpAudioFocusInfo(mTopFocusInfo));
return false;
}
if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) {
switch (mCurrentFocusState.focusState) {
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
// should not have focus. So enqueue release
mFocusHandler.handleFocusReleaseRequest();
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo);
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
doHandleFocusLossTransientFromCar(mCurrentFocusState);
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState);
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
break;
}
if (mRadioActive) { // radio is no longer active.
mRadioActive = false;
}
return false;
}
mFocusHandler.cancelFocusReleaseRequest();
AudioAttributes attrib = mTopFocusInfo.getAttributes();
int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib);
int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
logicalStreamTypeForTop);
int audioContexts = 0;
if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) {
if (!mCallActive) {
mCallActive = true;
audioContexts |= AudioHalService.AUDIO_CONTEXT_CALL_FLAG;
}
} else {
if (mCallActive) {
mCallActive = false;
}
audioContexts = AudioHalService.logicalStreamToHalContextType(logicalStreamTypeForTop);
}
// other apps having focus
int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
int streamsToRequest = 0x1 << physicalStreamTypeForTop;
switch (mTopFocusInfo.getGainRequest()) {
case AudioManager.AUDIOFOCUS_GAIN:
if (isFocusFromRadio(mTopFocusInfo)) {
mRadioActive = true;
// audio context sending is only for audio from android.
audioContexts = 0;
} else {
mRadioActive = false;
}
focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
break;
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
// radio cannot be active
mRadioActive = false;
focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
break;
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
audioContexts |= getAudioContext(mSecondFocusInfo);
focusToRequest =
AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
switch (mCurrentFocusState.focusState) {
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
streamsToRequest |= mCurrentFocusState.streams;
focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
streamsToRequest |= mCurrentFocusState.streams;
focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
return false;
}
break;
default:
streamsToRequest = 0;
break;
}
if (mRadioActive) {
// TODO any need to keep media stream while radio is active?
// Most cars do not allow that, but if mixing is possible, it can take media stream.
// For now, assume no mixing capability.
int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
CarAudioManager.CAR_AUDIO_USAGE_MUSIC);
if (!isFocusFromRadio(mTopFocusInfo) &&
(physicalStreamTypeForTop == radioPhysicalStream)) {
Log.i(CarLog.TAG_AUDIO, "Top stream is taking the same stream:" +
physicalStreamTypeForTop + " as radio, stopping radio");
// stream conflict here. radio cannot be played
extFocus = 0;
mRadioActive = false;
audioContexts &= ~AudioHalService.AUDIO_CONTEXT_RADIO_FLAG;
} else {
extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
streamsToRequest &= ~(0x1 << radioPhysicalStream);
}
} else if (streamsToRequest == 0) {
mCurrentAudioContexts = 0;
mFocusHandler.handleFocusReleaseRequest();
return false;
}
return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus,
audioContexts);
}
private static int getAudioContext(AudioFocusInfo info) {
if (info == null) {
return 0;
}
AudioAttributes attrib = info.getAttributes();
if (attrib == null) {
return AudioHalService.AUDIO_CONTEXT_UNKNOWN_FLAG;
}
return AudioHalService.logicalStreamToHalContextType(
CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib));
}
private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest,
int streamsToRequest, int extFocus, int audioContexts) {
if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus,
audioContexts)) {
mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest,
extFocus);
mCurrentAudioContexts = audioContexts;
if (DBG) {
Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" +
Integer.toHexString(audioContexts));
}
mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus,
audioContexts);
try {
mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS);
} catch (InterruptedException e) {
//ignore
}
return true;
}
return false;
}
private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest,
int extFocus, int audioContexts) {
if (streamsToRequest != mCurrentFocusState.streams) {
return true;
}
if (audioContexts != mCurrentAudioContexts) {
return true;
}
if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) {
return true;
}
switch (focusToRequest) {
case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN:
if (mCurrentFocusState.focusState ==
AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) {
return false;
}
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT:
case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK:
if (mCurrentFocusState.focusState ==
AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN ||
mCurrentFocusState.focusState ==
AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) {
return false;
}
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
if (mCurrentFocusState.focusState ==
AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
mCurrentFocusState.focusState ==
AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
return false;
}
break;
}
return true;
}
private void doHandleAndroidFocusChange() {
boolean focusRequested = false;
synchronized (mLock) {
if (mPendingFocusChanges.isEmpty()) {
// no entry. It was handled already.
if (DBG) {
Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty");
}
return;
}
AudioFocusInfo newTopInfo = mPendingFocusChanges.getFirst();
mPendingFocusChanges.clear();
if (mTopFocusInfo != null &&
newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) &&
newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() &&
isAudioAttributesSame(
newTopInfo.getAttributes(), mTopFocusInfo.getAttributes())) {
if (DBG) {
Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" +
dumpAudioFocusInfo(mTopFocusInfo));
}
// already in top somehow, no need to make any change
return;
}
if (DBG) {
Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo));
}
if (newTopInfo.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
mSecondFocusInfo = mTopFocusInfo;
} else {
mSecondFocusInfo = null;
}
mTopFocusInfo = newTopInfo;
focusRequested = reevaluateCarAudioFocusLocked();
if (DBG) {
if (!focusRequested) {
Log.i(TAG_FOCUS, "focus not requested for top focus:" +
dumpAudioFocusInfo(newTopInfo) + " currentState:" + mCurrentFocusState);
}
}
if (focusRequested && mFocusReceived == null) {
Log.w(TAG_FOCUS, "focus response timed out, request sent" +
mLastFocusRequestToCar);
// no response. so reset to loss.
mFocusReceived = FocusState.STATE_LOSS;
mCurrentAudioContexts = 0;
}
}
// handle it if there was response or force handle it for timeout.
if (focusRequested) {
doHandleCarFocusChange();
}
}
private void doHandleFocusRelease() {
//TODO Is there a need to wait for the stopping of streams?
boolean sent = false;
synchronized (mLock) {
if (mCurrentFocusState != FocusState.STATE_LOSS) {
if (DBG) {
Log.d(TAG_FOCUS, "focus release to car");
}
mLastFocusRequestToCar = FocusRequest.STATE_RELEASE;
sent = true;
mAudioHal.requestAudioFocusChange(
AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
try {
mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS);
} catch (InterruptedException e) {
//ignore
}
} else if (DBG) {
Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss");
}
}
// handle it if there was response.
if (sent) {
doHandleCarFocusChange();
}
}
private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) {
if (one.getContentType() != two.getContentType()) {
return false;
}
if (one.getUsage() != two.getUsage()) {
return false;
}
return true;
}
private static String dumpAudioFocusInfo(AudioFocusInfo info) {
StringBuilder builder = new StringBuilder();
builder.append("afi package:" + info.getPackageName());
builder.append("client id:" + info.getClientId());
builder.append(",gain:" + info.getGainRequest());
builder.append(",loss:" + info.getLossReceived());
builder.append(",flag:" + info.getFlags());
AudioAttributes attrib = info.getAttributes();
if (attrib != null) {
builder.append("," + attrib.toString());
}
return builder.toString();
}
private class SystemFocusListener extends AudioPolicyFocusListener {
@Override
public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
if (afi == null) {
return;
}
if (DBG) {
Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) +
" result:" + requestResult);
}
if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
synchronized (mLock) {
mPendingFocusChanges.addFirst(afi);
}
mFocusHandler.handleAndroidFocusChange();
}
}
@Override
public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
if (DBG) {
Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) +
" notified:" + wasNotified);
}
// ignore loss as tracking gain is enough. At least bottom listener will be
// always there and getting focus grant. So it is safe to ignore this here.
}
}
/**
* Focus listener to take focus away from android apps as a proxy to car.
*/
private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
@Override
public void onAudioFocusChange(int focusChange) {
// Do not need to handle car's focus loss or gain separately. Focus monitoring
// through system focus listener will take care all cases.
}
}
/**
* Focus listener kept at the bottom to check if there is any focus holder.
*
*/
private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
@Override
public void onAudioFocusChange(int focusChange) {
synchronized (mLock) {
mBottomFocusState = focusChange;
}
}
}
private class CarAudioFocusChangeHandler extends Handler {
private static final int MSG_FOCUS_CHANGE = 0;
private static final int MSG_STREAM_STATE_CHANGE = 1;
private static final int MSG_ANDROID_FOCUS_CHANGE = 2;
private static final int MSG_FOCUS_RELEASE = 3;
/** Focus release is always delayed this much to handle repeated acquire / release. */
private static final long FOCUS_RELEASE_DELAY_MS = 500;
private CarAudioFocusChangeHandler(Looper looper) {
super(looper);
}
private void handleFocusChange() {
Message msg = obtainMessage(MSG_FOCUS_CHANGE);
sendMessage(msg);
}
private void handleStreamStateChange(int streamNumber, int state) {
Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber, state);
sendMessage(msg);
}
private void handleAndroidFocusChange() {
Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
sendMessage(msg);
}
private void handleFocusReleaseRequest() {
if (DBG) {
Log.d(TAG_FOCUS, "handleFocusReleaseRequest");
}
cancelFocusReleaseRequest();
Message msg = obtainMessage(MSG_FOCUS_RELEASE);
sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS);
}
private void cancelFocusReleaseRequest() {
removeMessages(MSG_FOCUS_RELEASE);
}
private void cancelAll() {
removeMessages(MSG_FOCUS_CHANGE);
removeMessages(MSG_STREAM_STATE_CHANGE);
removeMessages(MSG_ANDROID_FOCUS_CHANGE);
removeMessages(MSG_FOCUS_RELEASE);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_FOCUS_CHANGE:
doHandleCarFocusChange();
break;
case MSG_STREAM_STATE_CHANGE:
doHandleStreamStatusChange(msg.arg1, msg.arg2);
break;
case MSG_ANDROID_FOCUS_CHANGE:
doHandleAndroidFocusChange();
break;
case MSG_FOCUS_RELEASE:
doHandleFocusRelease();
break;
}
}
}
private class CarAudioVolumeHandler extends Handler {
private static final int MSG_VOLUME_CHANGE = 0;
private CarAudioVolumeHandler(Looper looper) {
super(looper);
}
private void handleVolumeChange(VolumeStateChangeEvent event) {
Message msg = obtainMessage(MSG_VOLUME_CHANGE, event);
sendMessage(msg);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_VOLUME_CHANGE:
doHandleVolumeChange((VolumeStateChangeEvent) msg.obj);
break;
}
}
}
private static class VolumeStateChangeEvent {
public final int stream;
public final int volume;
public final int state;
public VolumeStateChangeEvent(int stream, int volume, int state) {
this.stream = stream;
this.volume = volume;
this.state = state;
}
}
/** Wrapper class for holding the current focus state from car. */
private static class FocusState {
public final int focusState;
public final int streams;
public final int externalFocus;
private FocusState(int focusState, int streams, int externalFocus) {
this.focusState = focusState;
this.streams = streams;
this.externalFocus = externalFocus;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof FocusState)) {
return false;
}
FocusState that = (FocusState) o;
return this.focusState == that.focusState && this.streams == that.streams &&
this.externalFocus == that.externalFocus;
}
@Override
public String toString() {
return "FocusState, state:" + focusState +
" streams:0x" + Integer.toHexString(streams) +
" externalFocus:0x" + Integer.toHexString(externalFocus);
}
public static FocusState create(int focusState, int streams, int externalAudios) {
return new FocusState(focusState, streams, externalAudios);
}
public static FocusState create(int[] state) {
return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE],
state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS],
state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]);
}
public static FocusState STATE_LOSS =
new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0);
}
/** Wrapper class for holding the focus requested to car. */
private static class FocusRequest {
public final int focusRequest;
public final int streams;
public final int externalFocus;
private FocusRequest(int focusRequest, int streams, int externalFocus) {
this.focusRequest = focusRequest;
this.streams = streams;
this.externalFocus = externalFocus;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof FocusRequest)) {
return false;
}
FocusRequest that = (FocusRequest) o;
return this.focusRequest == that.focusRequest && this.streams == that.streams &&
this.externalFocus == that.externalFocus;
}
@Override
public String toString() {
return "FocusRequest, request:" + focusRequest +
" streams:0x" + Integer.toHexString(streams) +
" externalFocus:0x" + Integer.toHexString(externalFocus);
}
public static FocusRequest create(int focusRequest, int streams, int externalFocus) {
switch (focusRequest) {
case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
return STATE_RELEASE;
}
return new FocusRequest(focusRequest, streams, externalFocus);
}
public static FocusRequest STATE_RELEASE =
new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
}
}