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

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

Merge "Synchronous audio focus behavior with external focus policy"

parents cc2e2d46 e2d8aae2
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -2516,6 +2516,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