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

Commit 136f5415 authored by Jean-Michel Trivi's avatar Jean-Michel Trivi Committed by Android (Google) Code Review
Browse files

Merge "Support for delayed audio focus" into lmp-mr1-dev

parents 87bc2acf fd6ad747
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
@@ -5011,10 +5011,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)