summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2018-01-23 16:46:10 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2018-01-23 16:46:10 +0000
commitd66cfdfc9a13be412a5a832149071ea3154e4a6f (patch)
treea3d6abaac44c655646dfed976fc295384d7c143a
parent4953824b89bfbdd5a85a2a7182c0903d9283e1d1 (diff)
parent9228af6bc20c27b9949df36684f9c06ca9cdb27d (diff)
Merge "AudioFocusRequest: add ability to force ducking for a11y"
-rw-r--r--api/current.txt1
-rw-r--r--media/java/android/media/AudioFocusRequest.java31
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java42
-rw-r--r--services/core/java/com/android/server/audio/FocusRequester.java19
-rw-r--r--services/core/java/com/android/server/audio/MediaFocusControl.java18
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java6
-rw-r--r--services/core/java/com/android/server/audio/PlayerFocusEnforcer.java2
7 files changed, 98 insertions, 21 deletions
diff --git a/api/current.txt b/api/current.txt
index 23a278e6672e..812134f1a280 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -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);
diff --git a/media/java/android/media/AudioFocusRequest.java b/media/java/android/media/AudioFocusRequest.java
index de59ac39abf6..7104dad4dc4c 100644
--- a/media/java/android/media/AudioFocusRequest.java
+++ b/media/java/android/media/AudioFocusRequest.java
@@ -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
@@ -526,6 +531,21 @@ public final class AudioFocusRequest {
}
/**
+ * 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.
* @return the {@code AudioFocusRequest} instance qualified by all the properties set
@@ -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)
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index a7147206bda2..f4c99f5e5491 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -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,
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index 48f0d5a11e0a..f2ef02fb2579 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -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.
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index de58b59a2423..9ddc52a1826e 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -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
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 494317334f43..ff864536e9af 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -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()
diff --git a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
index 0733eca97d4e..3c834daf3c8a 100644
--- a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
+++ b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
@@ -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);