diff options
| author | 2018-01-23 16:46:10 +0000 | |
|---|---|---|
| committer | 2018-01-23 16:46:10 +0000 | |
| commit | d66cfdfc9a13be412a5a832149071ea3154e4a6f (patch) | |
| tree | a3d6abaac44c655646dfed976fc295384d7c143a | |
| parent | 4953824b89bfbdd5a85a2a7182c0903d9283e1d1 (diff) | |
| parent | 9228af6bc20c27b9949df36684f9c06ca9cdb27d (diff) | |
Merge "AudioFocusRequest: add ability to force ducking for a11y"
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); |