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

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

Support collaborative audio focus handling

Add new flag for an app to define it doesn't duck, but rather
  pauses when losing focus with AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK.
  This flag is to be used when requesting focus.
Add support for AudioPolicy to specify whether it will implement
  ducking itself, rather than it being handled by an app.
When ducking is handled by a policy, do not notify focus owners
  when they lose audio focus with LOSS_TRANSIENT_CAN_DUCK, unless
  they would have paused, as expressed with the
  AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS flag.
Add a focus listener for a policy to be notified of focus changes
  so it can properly implement its own ducking.

Bug 16010554

Change-Id: I11d7cdb85c52fd086128a44f4d938aaa44db5c25
parent 7792b714
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -330,6 +330,7 @@ LOCAL_SRC_FILES += \
	media/java/android/media/IRemoteVolumeObserver.aidl \
	media/java/android/media/IRingtonePlayer.aidl \
	media/java/android/media/IVolumeController.aidl \
        media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl \
	media/java/android/media/projection/IMediaProjection.aidl \
	media/java/android/media/projection/IMediaProjectionCallback.aidl \
	media/java/android/media/projection/IMediaProjectionManager.aidl \
@@ -437,6 +438,7 @@ aidl_files := \
	frameworks/base/media/java/android/media/MediaDescription.aidl \
	frameworks/base/media/java/android/media/Rating.aidl \
	frameworks/base/media/java/android/media/AudioAttributes.aidl \
	frameworks/base/media/java/android/media/AudioFocusInfo.aidl \
	frameworks/base/media/java/android/media/session/PlaybackState.aidl \
	frameworks/base/media/java/android/media/session/MediaSession.aidl \
	frameworks/base/media/java/android/media/tv/TvInputInfo.aidl \
+18 −0
Original line number Diff line number Diff line
/* Copyright 2014, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/

package android.media;

parcelable AudioFocusInfo;
+175 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.media;

import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;

import java.util.Objects;

/**
 * @hide
 * A class to encapsulate information about an audio focus owner or request.
 */
@SystemApi
public final class AudioFocusInfo implements Parcelable {

    private AudioAttributes mAttributes;
    private String mClientId;
    private String mPackageName;
    private int mGainRequest;
    private int mLossReceived;
    private int mFlags;


    /**
     * Class constructor
     * @param aa
     * @param clientId
     * @param packageName
     * @param gainRequest
     * @param lossReceived
     * @param flags
     */
    AudioFocusInfo(AudioAttributes aa, String clientId, String packageName,
            int gainRequest, int lossReceived, int flags) {
        mAttributes = aa == null ? new AudioAttributes.Builder().build() : aa;
        mClientId = clientId == null ? "" : clientId;
        mPackageName = packageName == null ? "" : packageName;
        mGainRequest = gainRequest;
        mLossReceived = lossReceived;
        mFlags = flags;
    }


    /**
     * The audio attributes for the audio focus request.
     * @return non-null {@link AudioAttributes}.
     */
    @SystemApi
    public AudioAttributes getAttributes() { return mAttributes; }

    @SystemApi
    public String getClientId() { return mClientId; }

    @SystemApi
    public String getPackageName() { return mPackageName; }

    /**
     * The type of audio focus gain request.
     * @return one of {@link AudioManager#AUDIOFOCUS_GAIN},
     *     {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT},
     *     {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK},
     *     {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}.
     */
    @SystemApi
    public int getGainRequest() { return mGainRequest; }

    /**
     * The type of audio focus loss that was received by the
     * {@link AudioManager.OnAudioFocusChangeListener} if one was set.
     * @return 0 if focus wasn't lost, or one of {@link AudioManager#AUDIOFOCUS_LOSS},
     *   {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT} or
     *   {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
     */
    @SystemApi
    public int getLossReceived() { return mLossReceived; }

    /** @hide */
    void clearLossReceived() { mLossReceived = 0; }

    /**
     * The flags set in the audio focus request.
     * @return 0 or a combination of {link AudioManager#AUDIOFOCUS_FLAG_DELAY_OK},
     *     {@link AudioManager#AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS}, and
     *     {@link AudioManager#AUDIOFOCUS_FLAG_LOCK}.
     */
    @SystemApi
    public int getFlags() { return mFlags; }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        mAttributes.writeToParcel(dest, flags);
        dest.writeString(mClientId);
        dest.writeString(mPackageName);
        dest.writeInt(mGainRequest);
        dest.writeInt(mLossReceived);
        dest.writeInt(mFlags);
    }

    @SystemApi
    @Override
    public int hashCode() {
        return Objects.hash(mAttributes, mClientId, mPackageName, mGainRequest, mFlags);
    }

    @SystemApi
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        AudioFocusInfo other = (AudioFocusInfo) obj;
        if (!mAttributes.equals(other.mAttributes)) {
            return false;
        }
        if (!mClientId.equals(other.mClientId)) {
            return false;
        }
        if (!mPackageName.equals(other.mPackageName)) {
            return false;
        }
        if (mGainRequest != other.mGainRequest) {
            return false;
        }
        if (mLossReceived != other.mLossReceived) {
            return false;
        }
        if (mFlags != other.mFlags) {
            return false;
        }
        return true;
    }

    public static final Parcelable.Creator<AudioFocusInfo> CREATOR
            = new Parcelable.Creator<AudioFocusInfo>() {

        public AudioFocusInfo createFromParcel(Parcel in) {
            return new AudioFocusInfo(
                    AudioAttributes.CREATOR.createFromParcel(in), //AudioAttributes aa
                    in.readString(), //String clientId
                    in.readString(), //String packageName
                    in.readInt(), //int gainRequest
                    in.readInt(), //int lossReceived
                    in.readInt() //int flags
                    );
        }

        public AudioFocusInfo[] newArray(int size) {
            return new AudioFocusInfo[size];
        }
    };
}
+37 −20
Original line number Diff line number Diff line
@@ -2368,17 +2368,42 @@ public class AudioManager {
    }

    // when adding new flags, add them to the relevant AUDIOFOCUS_FLAGS_APPS or SYSTEM masks
    /** @hide */
    /**
     * @hide
     * Use this flag when requesting audio focus to indicate 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.
     */
    @SystemApi
    public static final int AUDIOFOCUS_FLAG_DELAY_OK = 0x1 << 0;
    /** @hide */
    /**
     * @hide
     * Use this flag when requesting audio focus to indicate that the requester
     * will pause its media playback (if applicable) when losing audio focus with
     * {@link #AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}, rather than ducking.
     * <br>On some platforms, the ducking may be handled without the application being aware of it
     * (i.e. it will not transiently lose focus). For applications that for instance play spoken
     * content, such as audio book or podcast players, ducking may never be acceptable, and will
     * thus always pause. This flag enables them to be declared as such whenever they request focus.
     */
    @SystemApi
    public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 0x1 << 1;
    /**
     * @hide
     * Use this flag to lock audio focus so granting is temporarily disabled.
     * <br>This flag can only be used by owners of a registered
     * {@link android.media.audiopolicy.AudioPolicy} in
     * {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int, AudioPolicy)}
     */
    @SystemApi
    public static final int AUDIOFOCUS_FLAG_LOCK     = 0x1 << 1;
    public static final int AUDIOFOCUS_FLAG_LOCK     = 0x1 << 2;
    /** @hide */
    public static final int AUDIOFOCUS_FLAGS_APPS = AUDIOFOCUS_FLAG_DELAY_OK;
    public static final int AUDIOFOCUS_FLAGS_APPS = AUDIOFOCUS_FLAG_DELAY_OK
            | AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS;
    /** @hide */
    public static final int AUDIOFOCUS_FLAGS_SYSTEM = AUDIOFOCUS_FLAG_DELAY_OK
            | AUDIOFOCUS_FLAG_LOCK;
            | AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS | AUDIOFOCUS_FLAG_LOCK;

    /**
     * @hide
@@ -2400,15 +2425,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 0 or {link #AUDIOFOCUS_FLAG_DELAY_OK}.
     * @param flags 0 or a combination of {link #AUDIOFOCUS_FLAG_DELAY_OK}
     *     and {@link #AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS}.
     *     <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.
     * @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
@@ -2442,17 +2464,11 @@ public class AudioManager {
     * @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}
     *     {@link #AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS}, and {@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
@@ -2493,7 +2509,7 @@ public class AudioManager {
            status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
                    mAudioFocusDispatcher, getIdForAudioFocusListener(l),
                    mContext.getOpPackageName() /* package name */, flags,
                    ap != null ? ap.token() : null);
                    ap != null ? ap.cb() : null);
        } catch (RemoteException e) {
            Log.e(TAG, "Can't call requestAudioFocus() on AudioService:", e);
        }
@@ -2870,7 +2886,8 @@ public class AudioManager {
        }
        IAudioService service = getService();
        try {
            String regId = service.registerAudioPolicy(policy.getConfig(), policy.token());
            String regId = service.registerAudioPolicy(policy.getConfig(), policy.cb(),
                    policy.hasFocusListener());
            if (regId == null) {
                return ERROR;
            } else {
@@ -2895,7 +2912,7 @@ public class AudioManager {
        }
        IAudioService service = getService();
        try {
            service.unregisterAudioPolicyAsync(policy.token());
            service.unregisterAudioPolicyAsync(policy.cb());
            policy.setRegistration(null);
        } catch (RemoteException e) {
            Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e);
+88 −26
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ import android.media.MediaPlayer.OnErrorListener;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicyConfig;
import android.media.audiopolicy.IAudioPolicyCallback;
import android.os.Binder;
import android.os.Build;
import android.os.Environment;
@@ -5078,7 +5079,7 @@ public class AudioService extends IAudioService.Stub {
    //==========================================================================================
    public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
            IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
            IBinder policyToken) {
            IAudioPolicyCallback pcb) {
        // permission checks
        if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {
            if (mMediaFocusControl.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) {
@@ -5090,9 +5091,8 @@ public class AudioService extends IAudioService.Stub {
            } 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());
                    if (!mAudioPolicies.containsKey(pcb.asBinder())) {
                        Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus");
                        return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                    }
                }
@@ -5786,30 +5786,34 @@ public class AudioService extends IAudioService.Stub {
    //==========================================================================================
    // Audio policy management
    //==========================================================================================
    public String registerAudioPolicy(AudioPolicyConfig policyConfig, IBinder cb) {
        //Log.v(TAG, "registerAudioPolicy for " + cb + " got policy:" + policyConfig);
    public String registerAudioPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb,
            boolean hasFocusListener) {
        if (DEBUG_AP) Log.d(TAG, "registerAudioPolicy for " + pcb.asBinder()
                + " with config:" + policyConfig);
        String regId = null;
        // error handling
        boolean hasPermissionForPolicy =
                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
                        android.Manifest.permission.MODIFY_AUDIO_ROUTING));
        if (!hasPermissionForPolicy) {
            Slog.w(TAG, "Can't register audio policy for pid " + Binder.getCallingPid() + " / uid "
                    + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING");
            return null;
        }

        synchronized (mAudioPolicies) {
            try {
                if (mAudioPolicies.containsKey(cb)) {
                if (mAudioPolicies.containsKey(pcb.asBinder())) {
                    Slog.e(TAG, "Cannot re-register policy");
                    return null;
                }
                AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
                cb.linkToDeath(app, 0/*flags*/);
                regId = app.connectMixes();
                mAudioPolicies.put(cb, app);
                AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener);
                pcb.asBinder().linkToDeath(app, 0/*flags*/);
                regId = app.getRegistrationId();
                mAudioPolicies.put(pcb.asBinder(), app);
            } catch (RemoteException e) {
                // audio policy owner has already died!
                Slog.w(TAG, "Audio policy registration failed, could not link to " + cb +
                Slog.w(TAG, "Audio policy registration failed, could not link to " + pcb +
                        " binder death", e);
                return null;
            }
@@ -5817,21 +5821,58 @@ public class AudioService extends IAudioService.Stub {
        return regId;
    }

    public void unregisterAudioPolicyAsync(IBinder cb) {
    public void unregisterAudioPolicyAsync(IAudioPolicyCallback pcb) {
        if (DEBUG_AP) Log.d(TAG, "unregisterAudioPolicyAsync for " + pcb.asBinder());
        synchronized (mAudioPolicies) {
            AudioPolicyProxy app = mAudioPolicies.remove(cb);
            AudioPolicyProxy app = mAudioPolicies.remove(pcb.asBinder());
            if (app == null) {
                Slog.w(TAG, "Trying to unregister unknown audio policy for pid "
                        + Binder.getCallingPid() + " / uid " + Binder.getCallingUid());
                return;
            } else {
                cb.unlinkToDeath(app, 0/*flags*/);
                pcb.asBinder().unlinkToDeath(app, 0/*flags*/);
            }
            app.disconnectMixes();
            app.release();
        }
        // TODO implement clearing mix attribute matching info in native audio policy
    }

    public int setFocusPropertiesForPolicy(int duckingBehavior, IAudioPolicyCallback pcb) {
        if (DEBUG_AP) Log.d(TAG, "setFocusPropertiesForPolicy() duck behavior=" + duckingBehavior
                + " policy " +  pcb.asBinder());
        // error handling
        boolean hasPermissionForPolicy =
                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
                        android.Manifest.permission.MODIFY_AUDIO_ROUTING));
        if (!hasPermissionForPolicy) {
            Slog.w(TAG, "Cannot change audio policy ducking handling for pid " +
                    + Binder.getCallingPid() + " / uid "
                    + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING");
            return AudioManager.ERROR;
        }

        synchronized (mAudioPolicies) {
            if (!mAudioPolicies.containsKey(pcb.asBinder())) {
                Slog.e(TAG, "Cannot change audio policy focus properties, unregistered policy");
                return AudioManager.ERROR;
            }
            final AudioPolicyProxy app = mAudioPolicies.get(pcb.asBinder());
            if (duckingBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) {
                // is there already one policy managing ducking?
                for(AudioPolicyProxy policy : mAudioPolicies.values()) {
                    if (policy.mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) {
                        Slog.e(TAG, "Cannot change audio policy ducking behavior, already handled");
                        return AudioManager.ERROR;
                    }
                }
            }
            app.mFocusDuckBehavior = duckingBehavior;
            mMediaFocusControl.setDuckingInExtPolicyAvailable(
                    duckingBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY);
        }
        return AudioManager.SUCCESS;
    }

    private void dumpAudioPolicies(PrintWriter pw) {
        pw.println("\nAudio policies:");
        synchronized (mAudioPolicies) {
@@ -5851,27 +5892,48 @@ public class AudioService extends IAudioService.Stub {
    public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient {
        private static final String TAG = "AudioPolicyProxy";
        AudioPolicyConfig mConfig;
        IBinder mToken;
        AudioPolicyProxy(AudioPolicyConfig config, IBinder token) {
        IAudioPolicyCallback mPolicyToken;
        boolean mHasFocusListener;
        /**
         * Audio focus ducking behavior for an audio policy.
         * This variable reflects the value that was successfully set in
         * {@link AudioService#setFocusPropertiesForPolicy(int, IAudioPolicyCallback)}. This
         * implies that a value of FOCUS_POLICY_DUCKING_IN_POLICY means the corresponding policy
         * is handling ducking for audio focus.
         */
        int mFocusDuckBehavior = AudioPolicy.FOCUS_POLICY_DUCKING_DEFAULT;

        AudioPolicyProxy(AudioPolicyConfig config, IAudioPolicyCallback token,
                boolean hasFocusListener) {
            super(config);
            setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++));
            mToken = token;
            mPolicyToken = token;
            mHasFocusListener = hasFocusListener;
            if (mHasFocusListener) {
                mMediaFocusControl.addFocusFollower(mPolicyToken);
            }
            updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE);
        }

        public void binderDied() {
            synchronized (mAudioPolicies) {
                Log.i(TAG, "audio policy " + mToken + " died");
                disconnectMixes();
                mAudioPolicies.remove(mToken);
                Log.i(TAG, "audio policy " + mPolicyToken + " died");
                release();
                mAudioPolicies.remove(mPolicyToken.asBinder());
            }
        }

        String connectMixes() {
            updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE);
        String getRegistrationId() {
            return getRegistration();
        }

        void disconnectMixes() {
        void release() {
            if (mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) {
                mMediaFocusControl.setDuckingInExtPolicyAvailable(false);
            }
            if (mHasFocusListener) {
                mMediaFocusControl.removeFocusFollower(mPolicyToken);
            }
            updateMixes(AudioSystem.DEVICE_STATE_UNAVAILABLE);
        }

Loading