summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jean-Michel Trivi <jmtrivi@google.com> 2018-01-05 17:06:17 -0800
committer Jean-Michel Trivi <jmtrivi@google.com> 2018-01-20 15:51:01 -0800
commit9228af6bc20c27b9949df36684f9c06ca9cdb27d (patch)
tree2de7f9b8583c445cda2222a55442e261806b33de
parent51a9a31ba58171c147b4c0c16c6acd54a97a5f65 (diff)
AudioFocusRequest: add ability to force ducking for a11y
New API to specify an accessbility service wants to force ducking in its focus request, regardless of the framework's evaluation of whether or not it should duck the other players. Use of this API is restricted to requests coming from services bound to the A11y service, or root Bug: 62194333 Test: play a podcast in GPM and "adb shell claf --gain 3 --usage 11 --forceDuck" Change-Id: I85f26afa14fe233a3b870f86c9084613203494b8
-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 3e4dfd217dcc..89619e250a57 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21693,6 +21693,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);