Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d66cfdfc authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

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

parents 4953824b 9228af6b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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);
+31 −0
Original line number Diff line number Diff line
@@ -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)
+41 −1
Original line number Diff line number Diff line
@@ -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,
+11 −8
Original line number Diff line number Diff line
@@ -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.
+10 −8
Original line number Diff line number Diff line
@@ -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
Loading