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

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

Synchronous audio focus behavior with external focus policy

This patch preserves the synchronous nature of audio focus requests,
  even when an external focus policy is installed.
When focus is requested, the request is blocked on
  the client-side, while AudioService informs the external
  policy of the request, and until the ext policy responds
  with the focus request result for this client, or
  it times out.
The new AudioPolicy API is the call for the external policy
  to send the focus request result.

Bug: 63906162
Test: gts-tradefed run gts -m GtsGmscoreHostTestCases -t 'com.google.android.gts.audio.AudioHostTest#testFocusPolicy'

Change-Id: I4671517f7f00eaaed8748bd4013b7d20be3085fb
parent 5ecc69ef
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -2513,6 +2513,7 @@ package android.media {
    method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
    method public deprecated int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException;
    method public int requestAudioFocus(android.media.AudioFocusRequest, android.media.audiopolicy.AudioPolicy);
    method public void setFocusRequestResult(android.media.AudioFocusInfo, int, android.media.audiopolicy.AudioPolicy);
    method public void unregisterAudioPolicyAsync(android.media.audiopolicy.AudioPolicy);
    field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
    field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
+20 −1
Original line number Diff line number Diff line
@@ -38,6 +38,10 @@ public final class AudioFocusInfo implements Parcelable {
    private int mLossReceived;
    private int mFlags;

    // generation count for the validity of a request/response async exchange between
    // external focus policy and MediaFocusControl
    private long mGenCount = -1;


    /**
     * Class constructor
@@ -61,6 +65,16 @@ public final class AudioFocusInfo implements Parcelable {
        mSdkTarget = sdk;
    }

    /** @hide */
    public void setGen(long g) {
        mGenCount = g;
    }

    /** @hide */
    public long getGen() {
        return mGenCount;
    }


    /**
     * The audio attributes for the audio focus request.
@@ -128,6 +142,7 @@ public final class AudioFocusInfo implements Parcelable {
        dest.writeInt(mLossReceived);
        dest.writeInt(mFlags);
        dest.writeInt(mSdkTarget);
        dest.writeLong(mGenCount);
    }

    @Override
@@ -168,6 +183,8 @@ public final class AudioFocusInfo implements Parcelable {
        if (mSdkTarget != other.mSdkTarget) {
            return false;
        }
        // mGenCount is not used to verify equality between two focus holds as multiple requests
        // (hence of different generations) could correspond to the same hold
        return true;
    }

@@ -175,7 +192,7 @@ public final class AudioFocusInfo implements Parcelable {
            = new Parcelable.Creator<AudioFocusInfo>() {

        public AudioFocusInfo createFromParcel(Parcel in) {
            return new AudioFocusInfo(
            final AudioFocusInfo afi = new AudioFocusInfo(
                    AudioAttributes.CREATOR.createFromParcel(in), //AudioAttributes aa
                    in.readInt(), // int clientUid
                    in.readString(), //String clientId
@@ -185,6 +202,8 @@ public final class AudioFocusInfo implements Parcelable {
                    in.readInt(), //int flags
                    in.readInt()  //int sdkTarget
                    );
            afi.setGen(in.readLong());
            return afi;
        }

        public AudioFocusInfo[] newArray(int size) {
+171 −11
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionLegacyHelper;
@@ -54,10 +55,13 @@ import android.util.Log;
import android.util.Slog;
import android.view.KeyEvent;

import com.android.internal.annotations.GuardedBy;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@@ -2338,6 +2342,20 @@ public class AudioManager {
                }
            }
        }

        @Override
        public void dispatchFocusResultFromExtPolicy(int requestResult, String clientId) {
            synchronized (mFocusRequestsLock) {
                // TODO use generation counter as the key instead
                final BlockingFocusResultReceiver focusReceiver =
                        mFocusRequestsAwaitingResult.remove(clientId);
                if (focusReceiver != null) {
                    focusReceiver.notifyResult(requestResult);
                } else {
                    Log.e(TAG, "dispatchFocusResultFromExtPolicy found no result receiver");
                }
            }
        }
    };

    private String getIdForAudioFocusListener(OnAudioFocusChangeListener l) {
@@ -2390,6 +2408,40 @@ public class AudioManager {
      */
    public static final int AUDIOFOCUS_REQUEST_DELAYED = 2;

    /** @hide */
    @IntDef(flag = false, prefix = "AUDIOFOCUS_REQUEST", value = {
            AUDIOFOCUS_REQUEST_FAILED,
            AUDIOFOCUS_REQUEST_GRANTED,
            AUDIOFOCUS_REQUEST_DELAYED }
    )
    @Retention(RetentionPolicy.SOURCE)
    public @interface FocusRequestResult {}

    /**
     * @hide
     * code returned when a synchronous focus request on the client-side is to be blocked
     * until the external audio focus policy decides on the response for the client
     */
    public static final int AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY = 100;

    /**
     * Timeout duration in ms when waiting on an external focus policy for the result for a
     * focus request
     */
    private static final int EXT_FOCUS_POLICY_TIMEOUT_MS = 200;

    private static final String FOCUS_CLIENT_ID_STRING = "android_audio_focus_client_id";

    private final Object mFocusRequestsLock = new Object();
    /**
     * Map of all receivers of focus request results, one per unresolved focus request.
     * Receivers are added before sending the request to the external focus policy,
     * and are removed either after receiving the result, or after the timeout.
     * This variable is lazily initialized.
     */
    @GuardedBy("mFocusRequestsLock")
    private HashMap<String, BlockingFocusResultReceiver> mFocusRequestsAwaitingResult;


    /**
     *  Request audio focus.
@@ -2656,19 +2708,101 @@ public class AudioManager {
            // some tests don't have a Context
            sdk = Build.VERSION.SDK_INT;
        }

        final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
        final BlockingFocusResultReceiver focusReceiver;
        synchronized (mFocusRequestsLock) {
            try {
                // TODO status contains result and generation counter for ext policy
                status = service.requestAudioFocus(afr.getAudioAttributes(),
                        afr.getFocusGain(), mICallBack,
                        mAudioFocusDispatcher,
                    getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener()),
                        clientId,
                        getContext().getOpPackageName() /* package name */, afr.getFlags(),
                        ap != null ? ap.cb() : null,
                        sdk);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
            if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
                // default path with no external focus policy
                return status;
            }
            if (mFocusRequestsAwaitingResult == null) {
                mFocusRequestsAwaitingResult =
                        new HashMap<String, BlockingFocusResultReceiver>(1);
            }
            focusReceiver = new BlockingFocusResultReceiver(clientId);
            mFocusRequestsAwaitingResult.put(clientId, focusReceiver);
        }
        focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS);
        if (DEBUG && !focusReceiver.receivedResult()) {
            Log.e(TAG, "requestAudio response from ext policy timed out, denying request");
        }
        synchronized (mFocusRequestsLock) {
            mFocusRequestsAwaitingResult.remove(clientId);
        }
        return focusReceiver.requestResult();
    }

    // helper class that abstracts out the handling of spurious wakeups in Object.wait()
    private static final class SafeWaitObject {
        private boolean mQuit = false;

        public void safeNotify() {
            synchronized (this) {
                mQuit = true;
                this.notify();
            }
        }

        public void safeWait(long millis) throws InterruptedException {
            final long timeOutTime = java.lang.System.currentTimeMillis() + millis;
            synchronized (this) {
                while (!mQuit) {
                    final long timeToWait = timeOutTime - java.lang.System.currentTimeMillis();
                    if (timeToWait < 0) { break; }
                    this.wait(timeToWait);
                }
            }
        }
    }

    private static final class BlockingFocusResultReceiver {
        private final SafeWaitObject mLock = new SafeWaitObject();
        @GuardedBy("mLock")
        private boolean mResultReceived = false;
        // request denied by default (e.g. timeout)
        private int mFocusRequestResult = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
        private final String mFocusClientId;

        BlockingFocusResultReceiver(String clientId) {
            mFocusClientId = clientId;
        }

        boolean receivedResult() { return mResultReceived; }
        int requestResult() { return mFocusRequestResult; }

        void notifyResult(int requestResult) {
            synchronized (mLock) {
                mResultReceived = true;
                mFocusRequestResult = requestResult;
                mLock.safeNotify();
            }
        }

        public void waitForResult(long timeOutMs) {
            synchronized (mLock) {
                if (mResultReceived) {
                    // the result was received before waiting
                    return;
                }
                try {
                    mLock.safeWait(timeOutMs);
                } catch (InterruptedException e) { }
            }
        }
    }

    /**
     * @hide
@@ -2712,6 +2846,32 @@ public class AudioManager {
        }
    }

    /**
     * @hide
     * Set the result to the audio focus request received through
     * {@link AudioPolicyFocusListener#onAudioFocusRequest(AudioFocusInfo, int)}.
     * @param afi the information about the focus requester
     * @param requestResult the result to the focus request to be passed to the requester
     * @param ap a valid registered {@link AudioPolicy} configured as a focus policy.
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
    public void setFocusRequestResult(@NonNull AudioFocusInfo afi,
            @FocusRequestResult int requestResult, @NonNull AudioPolicy ap) {
        if (afi == null) {
            throw new IllegalArgumentException("Illegal null AudioFocusInfo");
        }
        if (ap == null) {
            throw new IllegalArgumentException("Illegal null AudioPolicy");
        }
        final IAudioService service = getService();
        try {
            service.setFocusRequestResultFromExtPolicy(afi, requestResult, ap.cb());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * @hide
     * Notifies an application with a focus listener of gain or loss of audio focus.
+2 −0
Original line number Diff line number Diff line
@@ -25,4 +25,6 @@ oneway interface IAudioFocusDispatcher {

    void dispatchAudioFocusChange(int focusChange, String clientId);

    void dispatchFocusResultFromExtPolicy(int requestResult, String clientId);

}
+3 −0
Original line number Diff line number Diff line
@@ -207,5 +207,8 @@ interface IAudioService {
    int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(in BluetoothDevice device,
            int state, int profile, boolean suppressNoisyIntent);

    oneway void setFocusRequestResultFromExtPolicy(in AudioFocusInfo afi, int requestResult,
            in IAudioPolicyCallback pcb);

    // WARNING: read warning at top of file, it is recommended to add new methods at the end
}
Loading