Merge "AudioFocusRequest: add ability to force ducking for a11y"

This commit is contained in:
TreeHugger Robot
2018-01-23 16:46:10 +00:00
committed by Android (Google) Code Review
7 changed files with 98 additions and 21 deletions

View File

@@ -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);

View File

@@ -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)

View File

@@ -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,

View File

@@ -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.

View File

@@ -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

View File

@@ -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()

View File

@@ -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);