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

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

Support for delayed audio focus

New internal API for an audio focus requester to always enter
 the audio focus stack even if audio focus can't be granted
 immediately (e.g. during a phone call).
Remap the "no delay" interface to the new "requestAudioFocus"
 method signature and AIDL.

Bug 16010554

Change-Id: Iff91ddb0beb411cca1f8cf98300a9afc4178dc7f
parent 8c4aad50
Loading
Loading
Loading
Loading
+88 −7
Original line number Diff line number Diff line
@@ -2201,6 +2201,8 @@ public class AudioManager {
                            listener = findFocusListener((String)msg.obj);
                        }
                        if (listener != null) {
                            Log.d(TAG, "AudioManager dispatching onAudioFocusChange("
                                    + msg.what + ") for " + msg.obj);
                            listener.onAudioFocusChange(msg.what);
                        }
                    }
@@ -2270,6 +2272,14 @@ public class AudioManager {
     * A successful focus change request.
     */
    public static final int AUDIOFOCUS_REQUEST_GRANTED = 1;
     /**
      * @hide
      * A focus change request whose granting is delayed: the request was successful, but the
      * requester will only be granted audio focus once the condition that prevented immediate
      * granting has ended.
      * See {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
      */
    public static final int AUDIOFOCUS_REQUEST_DELAYED = 2;


    /**
@@ -2291,18 +2301,87 @@ public class AudioManager {
     */
    public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) {
        int status = AUDIOFOCUS_REQUEST_FAILED;

        try {
            // status is guaranteed to be either AUDIOFOCUS_REQUEST_FAILED or
            // AUDIOFOCUS_REQUEST_GRANTED as focus is requested without the
            // AUDIOFOCUS_FLAG_DELAY_OK flag
            status = requestAudioFocus(l,
                    new AudioAttributes.Builder()
                            .setInternalLegacyStreamType(streamType).build(),
                    durationHint,
                    0 /* flags, legacy behavior */);
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Audio focus request denied due to ", e);
        }

        return status;
    }

    // when adding new flags, add them to AUDIOFOCUS_FLAGS_ALL
    /** @hide */
    public static final int AUDIOFOCUS_FLAG_DELAY_OK = 0x1 << 0;
    /** @hide */
    public static final int AUDIOFOCUS_FLAGS_ALL = AUDIOFOCUS_FLAG_DELAY_OK;

    /**
     * @hide
     * @param l the listener to be notified of audio focus changes. It is not allowed to be null
     *     when the request is flagged with {@link #AUDIOFOCUS_FLAG_DELAY_OK}.
     * @param requestAttributes non null {@link AudioAttributes} describing the main reason for
     *     requesting audio focus.
     * @param durationHint use {@link #AUDIOFOCUS_GAIN_TRANSIENT} to indicate this focus request
     *      is temporary, and focus will be abandonned shortly. Examples of transient requests are
     *      for the playback of driving directions, or notifications sounds.
     *      Use {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} to indicate also that it's ok for
     *      the previous focus owner to keep playing if it ducks its audio output.
     *      Alternatively use {@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE} for a temporary request
     *      that benefits from the system not playing disruptive sounds like notifications, for
     *      usecases such as voice memo recording, or speech recognition.
     *      Use {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such
     *      as the playback of a song or a video.
     * @param flags use 0 when not using any flags for the request, which behaves like
     *      {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio
     *      focus is granted immediately, or the grant request fails because the system is in a
     *      state where focus cannot change (e.g. a phone call).
     *      Use {link #AUDIOFOCUS_FLAG_DELAY_OK} if it is ok for the requester to not be granted
     *      audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when
     *      the system is in a state where focus cannot change, but be granted focus later when
     *      this condition ends.
     * @return {@link #AUDIOFOCUS_REQUEST_FAILED}, {@link #AUDIOFOCUS_REQUEST_GRANTED}
     *     or {@link #AUDIOFOCUS_REQUEST_DELAYED}.
     *     The return value is never {@link #AUDIOFOCUS_REQUEST_DELAYED} when focus is requested
     *     without the {@link #AUDIOFOCUS_FLAG_DELAY_OK} flag.
     * @throws IllegalArgumentException
     */
    public int requestAudioFocus(OnAudioFocusChangeListener l,
            AudioAttributes requestAttributes,
            int durationHint,
            int flags) throws IllegalArgumentException {
        // parameter checking
        if (requestAttributes == null) {
            throw new IllegalArgumentException("Illegal null AudioAttributes argument");
        }
        if ((durationHint < AUDIOFOCUS_GAIN) ||
                (durationHint > AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
            Log.e(TAG, "Invalid duration hint, audio focus request denied");
            return status;
            throw new IllegalArgumentException("Invalid duration hint");
        }
        if (flags != (flags & AUDIOFOCUS_FLAGS_ALL)) {
            throw new IllegalArgumentException("Illegal flags 0x"
                + Integer.toHexString(flags).toUpperCase());
        }
        if (((flags & AUDIOFOCUS_FLAG_DELAY_OK) == AUDIOFOCUS_FLAG_DELAY_OK) && (l == null)) {
            throw new IllegalArgumentException(
                    "Illegal null focus listener when flagged as accepting delayed focus grant");
        }

        int status = AUDIOFOCUS_REQUEST_FAILED;
        registerAudioFocusListener(l);
        //TODO protect request by permission check?
        IAudioService service = getService();
        try {
            status = service.requestAudioFocus(streamType, durationHint, mICallBack,
            status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
                    mAudioFocusDispatcher, getIdForAudioFocusListener(l),
                    mContext.getOpPackageName() /* package name */);
                    mContext.getOpPackageName() /* package name */, flags);
        } catch (RemoteException e) {
            Log.e(TAG, "Can't call requestAudioFocus() on AudioService due to "+e);
        }
@@ -2322,9 +2401,11 @@ public class AudioManager {
    public void requestAudioFocusForCall(int streamType, int durationHint) {
        IAudioService service = getService();
        try {
            service.requestAudioFocus(streamType, durationHint, mICallBack, null,
            service.requestAudioFocus(new AudioAttributes.Builder()
                        .setInternalLegacyStreamType(streamType).build(),
                    durationHint, mICallBack, null,
                    MediaFocusControl.IN_VOICE_COMM_FOCUS_ID,
                    mContext.getOpPackageName());
                    mContext.getOpPackageName(), 0 /* flags, legacy behavior*/ );
        } catch (RemoteException e) {
            Log.e(TAG, "Can't call requestAudioFocusForCall() on AudioService due to "+e);
        }
+4 −4
Original line number Diff line number Diff line
@@ -4997,10 +4997,10 @@ public class AudioService extends IAudioService.Stub {
    //==========================================================================================
    // Audio Focus
    //==========================================================================================
    public int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb,
            IAudioFocusDispatcher fd, String clientId, String callingPackageName) {
        return mMediaFocusControl.requestAudioFocus(mainStreamType, durationHint, cb, fd,
                clientId, callingPackageName);
    public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
            IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) {
        return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
                clientId, callingPackageName, flags);
    }

    public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId) {
+18 −7
Original line number Diff line number Diff line
@@ -44,20 +44,25 @@ class FocusRequester {
     * the audio focus gain request that caused the addition of this object in the focus stack.
     */
    private final int mFocusGainRequest;
    /**
     * the flags associated with the gain request that qualify the type of grant (e.g. accepting
     * delay vs grant must be immediate)
     */
    private final int mGrantFlags;
    /**
     * the audio focus loss received my mFocusDispatcher, is AudioManager.AUDIOFOCUS_NONE if
     *  it never lost focus.
     */
    private int mFocusLossReceived;
    /**
     * the stream type associated with the focus request
     * the audio attributes associated with the focus request
     */
    private final int mStreamType;
    private final AudioAttributes mAttributes;

    FocusRequester(int streamType, int focusRequest,
    FocusRequester(AudioAttributes aa, int focusRequest, int grantFlags,
            IAudioFocusDispatcher afl, IBinder source, String id, AudioFocusDeathHandler hdlr,
            String pn, int uid) {
        mStreamType = streamType;
        mAttributes = aa;
        mFocusDispatcher = afl;
        mSourceRef = source;
        mClientId = id;
@@ -65,6 +70,7 @@ class FocusRequester {
        mPackageName = pn;
        mCallingUid = uid;
        mFocusGainRequest = focusRequest;
        mGrantFlags = grantFlags;
        mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
    }

@@ -98,8 +104,12 @@ class FocusRequester {
        return mFocusGainRequest;
    }

    int getStreamType() {
        return mStreamType;
    int getGrantFlags() {
        return mGrantFlags;
    }

    AudioAttributes getAudioAttributes() {
        return mAttributes;
    }


@@ -139,9 +149,10 @@ class FocusRequester {
                + " -- pack: " + mPackageName
                + " -- client: " + mClientId
                + " -- gain: " + focusGainToString()
                + " -- grant: " + mGrantFlags
                + " -- loss: " + focusLossToString()
                + " -- uid: " + mCallingUid
                + " -- stream: " + mStreamType);
                + " -- attr: " + mAttributes);
    }


+3 −2
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.media;
import android.app.PendingIntent;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.media.AudioAttributes;
import android.media.AudioRoutesInfo;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioRoutesObserver;
@@ -116,8 +117,8 @@ interface IAudioService {

    boolean isBluetoothA2dpOn();

    int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb,
            IAudioFocusDispatcher fd, String clientId, String callingPackageName);
    int requestAudioFocus(in AudioAttributes aa, int durationHint, IBinder cb,
            IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags);

    int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId);

+74 −16
Original line number Diff line number Diff line
@@ -538,16 +538,54 @@ public class MediaFocusControl implements OnFinished {
    /**
     * Helper function:
     * Returns true if the system is in a state where the focus can be reevaluated, false otherwise.
     * The implementation guarantees that a state where focus cannot be immediately reassigned
     * implies that an "exclusive" focus owner is at the top of the focus stack.
     * Modifications to the implementation that break this assumption will cause focus requests to
     * misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag.
     */
    private boolean canReassignAudioFocus() {
        // focus requests are rejected during a phone call or when the phone is ringing
        // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus
        if (!mFocusStack.isEmpty() && mFocusStack.peek().hasSameClient(IN_VOICE_COMM_FOCUS_ID)) {
        if (!mFocusStack.isEmpty() && isExclusiveFocusOwner(mFocusStack.peek())) {
            return false;
        }
        return true;
    }

    private boolean isExclusiveFocusOwner(FocusRequester fr) {
        return fr.hasSameClient(IN_VOICE_COMM_FOCUS_ID);
    }

    /**
     * Helper function
     * Pre-conditions: focus stack is not empty, there is one or more exclusive focus owner
     *                 at the top of the focus stack
     * Push the focus requester onto the audio focus stack at the first position immediately
     * following the exclusive focus owners.
     * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or
     *     {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}
     */
    private int pushBelowExclusiveFocusOwners(FocusRequester nfr) {
        int lastExclusiveFocusOwnerIndex = mFocusStack.size();
        for (int index = mFocusStack.size()-1; index >= 0; index--) {
            if (isExclusiveFocusOwner(mFocusStack.elementAt(index))) {
                lastExclusiveFocusOwnerIndex = index;
            }
        }
        if (lastExclusiveFocusOwnerIndex == mFocusStack.size()) {
            // this should not happen, but handle it and log an error
            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());
            mFocusStack.push(nfr);
            return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
        } else {
            mFocusStack.insertElementAt(nfr, lastExclusiveFocusOwnerIndex);
            return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
        }
    }

    /**
     * Inner class to monitor audio focus client deaths, and remove them from the audio focus
     * stack if necessary.
@@ -581,10 +619,11 @@ public class MediaFocusControl implements OnFinished {
        }
    }

    /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int)  */
    protected int requestAudioFocus(int mainStreamType, int focusChangeHint, IBinder cb,
            IAudioFocusDispatcher fd, String clientId, String callingPackageName) {
        Log.i(TAG, " AudioFocus  requestAudioFocus() from " + clientId);
    /** @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) {
        Log.i(TAG, " AudioFocus  requestAudioFocus() from " + clientId + " req=" + focusChangeHint +
                "flags=0x" + Integer.toHexString(flags));
        // we need a valid binder callback for clients
        if (!cb.pingBinder()) {
            Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
@@ -597,8 +636,16 @@ public class MediaFocusControl implements OnFinished {
        }

        synchronized(mAudioFocusLock) {
            boolean focusGrantDelayed = false;
            if (!canReassignAudioFocus()) {
                if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                } else {
                    // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
                    // granted right now, so the requester will be inserted in the focus stack
                    // to receive focus later
                    focusGrantDelayed = true;
                }
            }

            // handle the potential premature death of the new holder of the focus
@@ -616,7 +663,8 @@ public class MediaFocusControl implements OnFinished {
            if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
                // if focus is already owned by this client and the reason for acquiring the focus
                // hasn't changed, don't do anything
                if (mFocusStack.peek().getGainRequest() == focusChangeHint) {
                final FocusRequester fr = mFocusStack.peek();
                if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
                    // unlink death handler so it can be gc'ed.
                    // linkToDeath() creates a JNI global reference preventing collection.
                    cb.unlinkToDeath(afdh, 0);
@@ -624,21 +672,31 @@ public class MediaFocusControl implements OnFinished {
                }
                // the reason for the audio focus request has changed: remove the current top of
                // stack and respond as if we had a new focus owner
                FocusRequester fr = mFocusStack.pop();
                if (!focusGrantDelayed) {
                    mFocusStack.pop();
                    // the entry that was "popped" is the same that was "peeked" above
                    fr.release();
                }
            }

            // focus requester might already be somewhere below in the stack, remove it
            removeFocusStackEntry(clientId, false /* signal */);

            final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
                    clientId, afdh, callingPackageName, Binder.getCallingUid());
            if (focusGrantDelayed) {
                // focusGrantDelayed being true implies we can't reassign focus right now
                // which implies the focus stack is not empty.
                return pushBelowExclusiveFocusOwners(nfr);
            } else {
                // propagate the focus change through the stack
                if (!mFocusStack.empty()) {
                    propagateFocusLossFromGain_syncAf(focusChangeHint);
                }

                // push focus requester at the top of the audio focus stack
            mFocusStack.push(new FocusRequester(mainStreamType, focusChangeHint, fd, cb,
                    clientId, afdh, callingPackageName, Binder.getCallingUid()));
                mFocusStack.push(nfr);
            }

        }//synchronized(mAudioFocusLock)