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

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

Add support for audio focus locking

New API for a registered AudioPolicy to lock/unlock the audio
  focus stack and prevent any new grant of focus, similar to
  the way phone calls behave.

Bug 16010554

Change-Id: If34a58ca9bd43d5479e94a2a7b540750b4c6efe9
parent 12c2136e
Loading
Loading
Loading
Loading
+98 −21
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.media;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
@@ -2318,14 +2319,25 @@ public class AudioManager {
        return status;
    }

    // when adding new flags, add them to AUDIOFOCUS_FLAGS_ALL
    // when adding new flags, add them to the relevant AUDIOFOCUS_FLAGS_APPS or SYSTEM masks
    /** @hide */
    @SystemApi
    public static final int AUDIOFOCUS_FLAG_DELAY_OK = 0x1 << 0;
    /** @hide */
    public static final int AUDIOFOCUS_FLAGS_ALL = AUDIOFOCUS_FLAG_DELAY_OK;
    @SystemApi
    public static final int AUDIOFOCUS_FLAG_LOCK     = 0x1 << 1;
    /** @hide */
    public static final int AUDIOFOCUS_FLAGS_APPS = AUDIOFOCUS_FLAG_DELAY_OK;
    /** @hide */
    public static final int AUDIOFOCUS_FLAGS_SYSTEM = AUDIOFOCUS_FLAG_DELAY_OK
            | AUDIOFOCUS_FLAG_LOCK;

    /**
     * @hide
     * Request audio focus.
     * Send a request to obtain the audio focus. This method differs from
     * {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)} in that it can express
     * that the requester accepts delayed grants of audio focus.
     * @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
@@ -2340,11 +2352,12 @@ public class AudioManager {
     *      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
     * @param flags 0 or {link #AUDIOFOCUS_FLAG_DELAY_OK}.
     *     <br>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
     *     <br>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.
@@ -2354,10 +2367,55 @@ public class AudioManager {
     *     without the {@link #AUDIOFOCUS_FLAG_DELAY_OK} flag.
     * @throws IllegalArgumentException
     */
    @SystemApi
    public int requestAudioFocus(OnAudioFocusChangeListener l,
            AudioAttributes requestAttributes,
            @NonNull AudioAttributes requestAttributes,
            int durationHint,
            int flags) throws IllegalArgumentException {
        if (flags != (flags & AUDIOFOCUS_FLAGS_APPS)) {
            throw new IllegalArgumentException("Invalid flags 0x"
                    + Integer.toHexString(flags).toUpperCase());
        }
        return requestAudioFocus(l, requestAttributes, durationHint,
                flags & AUDIOFOCUS_FLAGS_APPS,
                null /* no AudioPolicy*/);
    }

    /**
     * @hide
     * Request or lock audio focus.
     * This method is to be used by system components that have registered an
     * {@link android.media.audiopolicy.AudioPolicy} to request audio focus, but also to "lock" it
     * so focus granting is temporarily disabled.
     * @param l see the description of the same parameter in
     *     {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
     * @param requestAttributes non null {@link AudioAttributes} describing the main reason for
     *     requesting audio focus.
     * @param durationHint see the description of the same parameter in
     *     {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
     * @param flags 0 or a combination of {link #AUDIOFOCUS_FLAG_DELAY_OK},
     *     {@link #AUDIOFOCUS_FLAG_LOCK}
     *     <br>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).
     *     <br>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.
     *     <br>Use {@link #AUDIOFOCUS_FLAG_LOCK} when locking audio focus so granting is
     *     temporarily disabled.
     * @param ap a registered {@link android.media.audiopolicy.AudioPolicy} instance when locking
     *     focus, or null.
     * @return see the description of the same return value in
     *     {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
     * @throws IllegalArgumentException
     */
    public int requestAudioFocus(OnAudioFocusChangeListener l,
            @NonNull AudioAttributes requestAttributes,
            int durationHint,
            int flags,
            AudioPolicy ap) throws IllegalArgumentException {
        // parameter checking
        if (requestAttributes == null) {
            throw new IllegalArgumentException("Illegal null AudioAttributes argument");
@@ -2366,7 +2424,7 @@ public class AudioManager {
                (durationHint > AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
            throw new IllegalArgumentException("Invalid duration hint");
        }
        if (flags != (flags & AUDIOFOCUS_FLAGS_ALL)) {
        if (flags != (flags & AUDIOFOCUS_FLAGS_SYSTEM)) {
            throw new IllegalArgumentException("Illegal flags 0x"
                + Integer.toHexString(flags).toUpperCase());
        }
@@ -2374,6 +2432,10 @@ public class AudioManager {
            throw new IllegalArgumentException(
                    "Illegal null focus listener when flagged as accepting delayed focus grant");
        }
        if (((flags & AUDIOFOCUS_FLAG_LOCK) == AUDIOFOCUS_FLAG_LOCK) && (ap == null)) {
            throw new IllegalArgumentException(
                    "Illegal null audio policy when locking audio focus");
        }

        int status = AUDIOFOCUS_REQUEST_FAILED;
        registerAudioFocusListener(l);
@@ -2381,9 +2443,10 @@ public class AudioManager {
        try {
            status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
                    mAudioFocusDispatcher, getIdForAudioFocusListener(l),
                    mContext.getOpPackageName() /* package name */, flags);
                    mContext.getOpPackageName() /* package name */, flags,
                    ap != null ? ap.token() : null);
        } catch (RemoteException e) {
            Log.e(TAG, "Can't call requestAudioFocus() on AudioService due to "+e);
            Log.e(TAG, "Can't call requestAudioFocus() on AudioService:", e);
        }
        return status;
    }
@@ -2405,9 +2468,11 @@ public class AudioManager {
                        .setInternalLegacyStreamType(streamType).build(),
                    durationHint, mICallBack, null,
                    MediaFocusControl.IN_VOICE_COMM_FOCUS_ID,
                    mContext.getOpPackageName(), 0 /* flags, legacy behavior*/ );
                    mContext.getOpPackageName(),
                    AUDIOFOCUS_FLAG_LOCK,
                    null /* policy token */);
        } catch (RemoteException e) {
            Log.e(TAG, "Can't call requestAudioFocusForCall() on AudioService due to "+e);
            Log.e(TAG, "Can't call requestAudioFocusForCall() on AudioService:", e);
        }
    }

@@ -2420,9 +2485,10 @@ public class AudioManager {
    public void abandonAudioFocusForCall() {
        IAudioService service = getService();
        try {
            service.abandonAudioFocus(null, MediaFocusControl.IN_VOICE_COMM_FOCUS_ID);
            service.abandonAudioFocus(null, MediaFocusControl.IN_VOICE_COMM_FOCUS_ID,
                    null /*AudioAttributes, legacy behavior*/);
        } catch (RemoteException e) {
            Log.e(TAG, "Can't call abandonAudioFocusForCall() on AudioService due to "+e);
            Log.e(TAG, "Can't call abandonAudioFocusForCall() on AudioService:", e);
        }
    }

@@ -2432,19 +2498,30 @@ public class AudioManager {
     *  @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED}
     */
    public int abandonAudioFocus(OnAudioFocusChangeListener l) {
        return abandonAudioFocus(l, null /*AudioAttributes, legacy behavior*/);
    }

    /**
     * @hide
     * Abandon audio focus. Causes the previous focus owner, if any, to receive focus.
     *  @param l the listener with which focus was requested.
     * @param aa the {@link AudioAttributes} with which audio focus was requested
     * @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED}
     */
    @SystemApi
    public int abandonAudioFocus(OnAudioFocusChangeListener l, AudioAttributes aa) {
        int status = AUDIOFOCUS_REQUEST_FAILED;
        unregisterAudioFocusListener(l);
        IAudioService service = getService();
        try {
            status = service.abandonAudioFocus(mAudioFocusDispatcher,
                    getIdForAudioFocusListener(l));
                    getIdForAudioFocusListener(l), aa);
        } catch (RemoteException e) {
            Log.e(TAG, "Can't call abandonAudioFocus() on AudioService due to "+e);
            Log.e(TAG, "Can't call abandonAudioFocus() on AudioService:", e);
        }
        return status;
    }


    //====================================================================
    // Remote Control
    /**
+29 −4
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import android.hardware.usb.UsbManager;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicyConfig;
import android.os.Binder;
import android.os.Build;
@@ -5018,13 +5019,34 @@ public class AudioService extends IAudioService.Stub {
    // Audio Focus
    //==========================================================================================
    public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
            IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) {
            IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
            IBinder policyToken) {
        // permission checks
        if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {
            if (mMediaFocusControl.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) {
                if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
                            android.Manifest.permission.MODIFY_PHONE_STATE)) {
                    Log.e(TAG, "Invalid permission to (un)lock audio focus", new Exception());
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                }
            } else {
                // only a registered audio policy can be used to lock focus
                synchronized (mAudioPolicies) {
                    if (!mAudioPolicies.containsKey(policyToken)) {
                        Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus",
                                new Exception());
                        return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                    }
                }
            }
        }

        return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
                clientId, callingPackageName, flags);
    }

    public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId) {
        return mMediaFocusControl.abandonAudioFocus(fd, clientId);
    public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa) {
        return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa);
    }

    public void unregisterAudioFocusClient(String clientId) {
@@ -5725,6 +5747,9 @@ public class AudioService extends IAudioService.Stub {
        // TODO implement clearing mix attribute matching info in native audio policy
    }

    //======================
    // Audio policy proxy
    //======================
    /**
     * This internal class inherits from AudioPolicyConfig which contains all the mixes and
     * their configurations.
@@ -5742,8 +5767,8 @@ public class AudioService extends IAudioService.Stub {
        public void binderDied() {
            synchronized (mAudioPolicies) {
                Log.i(TAG, "audio policy " + mToken + " died");
                mAudioPolicies.remove(mToken);
                disconnectMixes();
                mAudioPolicies.remove(mToken);
            }
        }

+16 −1
Original line number Diff line number Diff line
@@ -83,6 +83,10 @@ class FocusRequester {
        }
    }

    boolean isLockedFocusOwner() {
        return ((mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0);
    }

    boolean hasSameBinder(IBinder ib) {
        return (mSourceRef != null) && mSourceRef.equals(ib);
    }
@@ -99,6 +103,9 @@ class FocusRequester {
        return mCallingUid == uid;
    }

    String getClientId() {
        return mClientId;
    }

    int getGainRequest() {
        return mFocusGainRequest;
@@ -144,12 +151,20 @@ class FocusRequester {
        return focusChangeToString(mFocusLossReceived);
    }

    private static String flagsToString(int flags) {
        String msg = new String();
        if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) != 0) { msg += "DELAY_OK"; }
        if (!msg.isEmpty()) { msg += "|"; }
        if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0)     { msg += "LOCK"; }
        return msg;
    }

    void dump(PrintWriter pw) {
        pw.println("  source:" + mSourceRef
                + " -- pack: " + mPackageName
                + " -- client: " + mClientId
                + " -- gain: " + focusGainToString()
                + " -- grant: " + mGrantFlags
                + " -- flags: " + flagsToString(mGrantFlags)
                + " -- loss: " + focusLossToString()
                + " -- uid: " + mCallingUid
                + " -- attr: " + mAttributes);
+3 −2
Original line number Diff line number Diff line
@@ -118,9 +118,10 @@ interface IAudioService {
    boolean isBluetoothA2dpOn();

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

    int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId);
    int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, in AudioAttributes aa);

    void unregisterAudioFocusClient(String clientId);

+20 −16
Original line number Diff line number Diff line
@@ -390,7 +390,8 @@ public class MediaFocusControl implements OnFinished {
    // AudioFocus
    //==========================================================================================

    /* constant to identify focus stack entry that is used to hold the focus while the phone
    /**
     * Constant to identify a focus stack entry that is used to hold the focus while the phone
     * is ringing or during a call. Used by com.android.internal.telephony.CallManager when
     * entering and exiting calls.
     */
@@ -539,40 +540,40 @@ 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.
     * implies that an "locked" 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() && isExclusiveFocusOwner(mFocusStack.peek())) {
        if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) {
            return false;
        }
        return true;
    }

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

    /**
     * Helper function
     * Pre-conditions: focus stack is not empty, there is one or more exclusive focus owner
     * Pre-conditions: focus stack is not empty, there is one or more locked 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.
     * following the locked focus owners.
     * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or
     *     {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}
     */
    private int pushBelowExclusiveFocusOwners(FocusRequester nfr) {
        int lastExclusiveFocusOwnerIndex = mFocusStack.size();
    private int pushBelowLockedFocusOwners(FocusRequester nfr) {
        int lastLockedFocusOwnerIndex = mFocusStack.size();
        for (int index = mFocusStack.size()-1; index >= 0; index--) {
            if (isExclusiveFocusOwner(mFocusStack.elementAt(index))) {
                lastExclusiveFocusOwnerIndex = index;
            if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
                lastLockedFocusOwnerIndex = index;
            }
        }
        if (lastExclusiveFocusOwnerIndex == mFocusStack.size()) {
        if (lastLockedFocusOwnerIndex == 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());
@@ -581,7 +582,7 @@ public class MediaFocusControl implements OnFinished {
            mFocusStack.push(nfr);
            return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
        } else {
            mFocusStack.insertElementAt(nfr, lastExclusiveFocusOwnerIndex);
            mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
            return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
        }
    }
@@ -687,7 +688,7 @@ public class MediaFocusControl implements OnFinished {
            if (focusGrantDelayed) {
                // focusGrantDelayed being true implies we can't reassign focus right now
                // which implies the focus stack is not empty.
                return pushBelowExclusiveFocusOwners(nfr);
                return pushBelowLockedFocusOwners(nfr);
            } else {
                // propagate the focus change through the stack
                if (!mFocusStack.empty()) {
@@ -703,8 +704,11 @@ public class MediaFocusControl implements OnFinished {
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    }

    /** @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener)  */
    protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId) {
    /**
     * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes)
     * */
    protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) {
        // AudioAttributes are currently ignored, to be used for zones
        Log.i(TAG, " AudioFocus  abandonAudioFocus() from " + clientId);
        try {
            // this will take care of notifying the new focus owner if needed