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

Commit 9228af6b authored by Jean-Michel Trivi's avatar Jean-Michel Trivi
Browse files

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
parent 51a9a31b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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);
+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