Merge "AudioFocusRequest: add ability to force ducking for a11y"
This commit is contained in:
committed by
Android (Google) Code Review
commit
d66cfdfc9a
@@ -21774,6 +21774,7 @@ package android.media {
|
||||
method public android.media.AudioFocusRequest.Builder setAcceptsDelayedFocusGain(boolean);
|
||||
method public android.media.AudioFocusRequest.Builder setAudioAttributes(android.media.AudioAttributes);
|
||||
method public android.media.AudioFocusRequest.Builder setFocusGain(int);
|
||||
method public android.media.AudioFocusRequest.Builder setForceDucking(boolean);
|
||||
method public android.media.AudioFocusRequest.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener);
|
||||
method public android.media.AudioFocusRequest.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler);
|
||||
method public android.media.AudioFocusRequest.Builder setWillPauseWhenDucked(boolean);
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.media.AudioManager.OnAudioFocusChangeListener;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
@@ -220,6 +221,9 @@ public final class AudioFocusRequest {
|
||||
private final static AudioAttributes FOCUS_DEFAULT_ATTR = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA).build();
|
||||
|
||||
/** @hide */
|
||||
public static final String KEY_ACCESSIBILITY_FORCE_FOCUS_DUCKING = "a11y_force_ducking";
|
||||
|
||||
private final OnAudioFocusChangeListener mFocusListener; // may be null
|
||||
private final Handler mListenerHandler; // may be null
|
||||
private final AudioAttributes mAttr; // never null
|
||||
@@ -349,6 +353,7 @@ public final class AudioFocusRequest {
|
||||
private boolean mPausesOnDuck = false;
|
||||
private boolean mDelayedFocus = false;
|
||||
private boolean mFocusLocked = false;
|
||||
private boolean mA11yForceDucking = false;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Builder}, and specifies how audio focus
|
||||
@@ -525,6 +530,21 @@ public final class AudioFocusRequest {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this focus request as forcing ducking, regardless of the conditions in which
|
||||
* the system would or would not enforce ducking.
|
||||
* Forcing ducking will only be honored when requesting AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
|
||||
* with an {@link AudioAttributes} usage of
|
||||
* {@link AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY}, coming from an accessibility
|
||||
* service, and will be ignored otherwise.
|
||||
* @param forceDucking {@code true} to force ducking
|
||||
* @return this {@code Builder} instance
|
||||
*/
|
||||
public @NonNull Builder setForceDucking(boolean forceDucking) {
|
||||
mA11yForceDucking = forceDucking;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@code AudioFocusRequest} instance combining all the information gathered
|
||||
* by this {@code Builder}'s configuration methods.
|
||||
@@ -538,6 +558,17 @@ public final class AudioFocusRequest {
|
||||
throw new IllegalStateException(
|
||||
"Can't use delayed focus or pause on duck without a listener");
|
||||
}
|
||||
if (mA11yForceDucking) {
|
||||
final Bundle extraInfo;
|
||||
if (mAttr.getBundle() == null) {
|
||||
extraInfo = new Bundle();
|
||||
} else {
|
||||
extraInfo = mAttr.getBundle();
|
||||
}
|
||||
// checking of usage and focus request is done server side
|
||||
extraInfo.putBoolean(KEY_ACCESSIBILITY_FORCE_FOCUS_DUCKING, true);
|
||||
mAttr = new AudioAttributes.Builder(mAttr).addBundle(extraInfo).build();
|
||||
}
|
||||
final int flags = 0
|
||||
| (mDelayedFocus ? AudioManager.AUDIOFOCUS_FLAG_DELAY_OK : 0)
|
||||
| (mPausesOnDuck ? AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS : 0)
|
||||
|
||||
@@ -63,6 +63,7 @@ import android.hardware.usb.UsbManager;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioDevicePort;
|
||||
import android.media.AudioFocusInfo;
|
||||
import android.media.AudioFocusRequest;
|
||||
import android.media.AudioSystem;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
@@ -6003,6 +6004,44 @@ public class AudioService extends IAudioService.Stub
|
||||
//==========================================================================================
|
||||
// Audio Focus
|
||||
//==========================================================================================
|
||||
/**
|
||||
* Returns whether a focus request is eligible to force ducking.
|
||||
* Will return true if:
|
||||
* - the AudioAttributes have a usage of USAGE_ASSISTANCE_ACCESSIBILITY,
|
||||
* - the focus request is AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
|
||||
* - the associated Bundle has KEY_ACCESSIBILITY_FORCE_FOCUS_DUCKING set to true,
|
||||
* - the uid of the requester is a known accessibility service or root.
|
||||
* @param aa AudioAttributes of the focus request
|
||||
* @param uid uid of the focus requester
|
||||
* @return true if ducking is to be forced
|
||||
*/
|
||||
private boolean forceFocusDuckingForAccessibility(@Nullable AudioAttributes aa,
|
||||
int request, int uid) {
|
||||
if (aa == null || aa.getUsage() != AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY
|
||||
|| request != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
|
||||
return false;
|
||||
}
|
||||
final Bundle extraInfo = aa.getBundle();
|
||||
if (extraInfo == null ||
|
||||
!extraInfo.getBoolean(AudioFocusRequest.KEY_ACCESSIBILITY_FORCE_FOCUS_DUCKING)) {
|
||||
return false;
|
||||
}
|
||||
if (uid == 0) {
|
||||
return true;
|
||||
}
|
||||
synchronized (mAccessibilityServiceUidsLock) {
|
||||
if (mAccessibilityServiceUids != null) {
|
||||
int callingUid = Binder.getCallingUid();
|
||||
for (int i = 0; i < mAccessibilityServiceUids.length; i++) {
|
||||
if (mAccessibilityServiceUids[i] == callingUid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
|
||||
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
|
||||
IAudioPolicyCallback pcb, int sdk) {
|
||||
@@ -6026,7 +6065,8 @@ public class AudioService extends IAudioService.Stub
|
||||
}
|
||||
|
||||
return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
|
||||
clientId, callingPackageName, flags, sdk);
|
||||
clientId, callingPackageName, flags, sdk,
|
||||
forceFocusDuckingForAccessibility(aa, durationHint, Binder.getCallingUid()));
|
||||
}
|
||||
|
||||
public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa,
|
||||
|
||||
@@ -307,9 +307,10 @@ public class FocusRequester {
|
||||
* @return true if the focus loss is definitive, false otherwise.
|
||||
*/
|
||||
@GuardedBy("MediaFocusControl.mAudioFocusLock")
|
||||
boolean handleFocusLossFromGain(int focusGain, final FocusRequester frWinner) {
|
||||
boolean handleFocusLossFromGain(int focusGain, final FocusRequester frWinner, boolean forceDuck)
|
||||
{
|
||||
final int focusLoss = focusLossForGainRequest(focusGain);
|
||||
handleFocusLoss(focusLoss, frWinner);
|
||||
handleFocusLoss(focusLoss, frWinner, forceDuck);
|
||||
return (focusLoss == AudioManager.AUDIOFOCUS_LOSS);
|
||||
}
|
||||
|
||||
@@ -343,7 +344,8 @@ public class FocusRequester {
|
||||
}
|
||||
|
||||
@GuardedBy("MediaFocusControl.mAudioFocusLock")
|
||||
void handleFocusLoss(int focusLoss, @Nullable final FocusRequester frWinner) {
|
||||
void handleFocusLoss(int focusLoss, @Nullable final FocusRequester frWinner, boolean forceDuck)
|
||||
{
|
||||
try {
|
||||
if (focusLoss != mFocusLossReceived) {
|
||||
mFocusLossReceived = focusLoss;
|
||||
@@ -374,19 +376,20 @@ public class FocusRequester {
|
||||
&& frWinner != null) {
|
||||
// candidate for enforcement by the framework
|
||||
if (frWinner.mCallingUid != this.mCallingUid) {
|
||||
if ((mGrantFlags
|
||||
& AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0) {
|
||||
if (!forceDuck && ((mGrantFlags
|
||||
& AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0)) {
|
||||
// the focus loser declared it would pause instead of duck, let it
|
||||
// handle it (the framework doesn't pause for apps)
|
||||
handled = false;
|
||||
Log.v(TAG, "not ducking uid " + this.mCallingUid + " - flags");
|
||||
} else if (MediaFocusControl.ENFORCE_DUCKING_FOR_NEW &&
|
||||
this.getSdkTarget() <= MediaFocusControl.DUCKING_IN_APP_SDK_LEVEL) {
|
||||
} else if (!forceDuck && (MediaFocusControl.ENFORCE_DUCKING_FOR_NEW &&
|
||||
this.getSdkTarget() <= MediaFocusControl.DUCKING_IN_APP_SDK_LEVEL))
|
||||
{
|
||||
// legacy behavior, apps used to be notified when they should be ducking
|
||||
handled = false;
|
||||
Log.v(TAG, "not ducking uid " + this.mCallingUid + " - old SDK");
|
||||
} else {
|
||||
handled = mFocusController.duckPlayers(frWinner, this);
|
||||
handled = mFocusController.duckPlayers(frWinner, this, forceDuck);
|
||||
}
|
||||
} // else: the focus change is within the same app, so let the dispatching
|
||||
// happen as if the framework was not involved.
|
||||
|
||||
@@ -101,8 +101,8 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
|
||||
//=================================================================
|
||||
// PlayerFocusEnforcer implementation
|
||||
@Override
|
||||
public boolean duckPlayers(FocusRequester winner, FocusRequester loser) {
|
||||
return mFocusEnforcer.duckPlayers(winner, loser);
|
||||
public boolean duckPlayers(FocusRequester winner, FocusRequester loser, boolean forceDuck) {
|
||||
return mFocusEnforcer.duckPlayers(winner, loser, forceDuck);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,7 +144,8 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
|
||||
if (!mFocusStack.empty()) {
|
||||
// notify the current focus owner it lost focus after removing it from stack
|
||||
final FocusRequester exFocusOwner = mFocusStack.pop();
|
||||
exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null);
|
||||
exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null,
|
||||
false /*forceDuck*/);
|
||||
exFocusOwner.release();
|
||||
}
|
||||
}
|
||||
@@ -166,13 +167,14 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
|
||||
* @param focusGain the new focus gain that will later be added at the top of the stack
|
||||
*/
|
||||
@GuardedBy("mAudioFocusLock")
|
||||
private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr) {
|
||||
private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,
|
||||
boolean forceDuck) {
|
||||
final List<String> clientsToRemove = new LinkedList<String>();
|
||||
// going through the audio focus stack to signal new focus, traversing order doesn't
|
||||
// matter as all entries respond to the same external focus gain
|
||||
for (FocusRequester focusLoser : mFocusStack) {
|
||||
final boolean isDefinitiveLoss =
|
||||
focusLoser.handleFocusLossFromGain(focusGain, fr);
|
||||
focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
|
||||
if (isDefinitiveLoss) {
|
||||
clientsToRemove.add(focusLoser.getClientId());
|
||||
}
|
||||
@@ -347,7 +349,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
|
||||
Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
|
||||
new Exception());
|
||||
// no exclusive owner, push at top of stack, focus is granted, propagate change
|
||||
propagateFocusLossFromGain_syncAf(nfr.getGainRequest(), nfr);
|
||||
propagateFocusLossFromGain_syncAf(nfr.getGainRequest(), nfr, false /*forceDuck*/);
|
||||
mFocusStack.push(nfr);
|
||||
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
|
||||
} else {
|
||||
@@ -664,7 +666,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
|
||||
/** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */
|
||||
protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
|
||||
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
|
||||
int sdk) {
|
||||
int sdk, boolean forceDuck) {
|
||||
mEventLogger.log((new AudioEventLogger.StringEvent(
|
||||
"requestAudioFocus() from uid/pid " + Binder.getCallingUid()
|
||||
+ "/" + Binder.getCallingPid()
|
||||
@@ -777,7 +779,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
|
||||
} else {
|
||||
// propagate the focus change through the stack
|
||||
if (!mFocusStack.empty()) {
|
||||
propagateFocusLossFromGain_syncAf(focusChangeHint, nfr);
|
||||
propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);
|
||||
}
|
||||
|
||||
// push focus requester at the top of the audio focus stack
|
||||
|
||||
@@ -421,7 +421,7 @@ public final class PlaybackActivityMonitor
|
||||
private final DuckingManager mDuckingManager = new DuckingManager();
|
||||
|
||||
@Override
|
||||
public boolean duckPlayers(FocusRequester winner, FocusRequester loser) {
|
||||
public boolean duckPlayers(FocusRequester winner, FocusRequester loser, boolean forceDuck) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, String.format("duckPlayers: uids winner=%d loser=%d",
|
||||
winner.getClientUid(), loser.getClientUid()));
|
||||
@@ -441,8 +441,8 @@ public final class PlaybackActivityMonitor
|
||||
&& loser.hasSameUid(apc.getClientUid())
|
||||
&& apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED)
|
||||
{
|
||||
if (apc.getAudioAttributes().getContentType() ==
|
||||
AudioAttributes.CONTENT_TYPE_SPEECH) {
|
||||
if (!forceDuck && (apc.getAudioAttributes().getContentType() ==
|
||||
AudioAttributes.CONTENT_TYPE_SPEECH)) {
|
||||
// the player is speaking, ducking will make the speech unintelligible
|
||||
// so let the app handle it instead
|
||||
Log.v(TAG, "not ducking player " + apc.getPlayerInterfaceId()
|
||||
|
||||
@@ -25,7 +25,7 @@ public interface PlayerFocusEnforcer {
|
||||
* @param loser
|
||||
* @return
|
||||
*/
|
||||
public boolean duckPlayers(FocusRequester winner, FocusRequester loser);
|
||||
public boolean duckPlayers(FocusRequester winner, FocusRequester loser, boolean forceDuck);
|
||||
|
||||
public void unduckPlayers(FocusRequester winner);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user