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

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

Merge "AudioMix address and type, rule exclusion API, dynamic source" into lmp-mr1-dev

parents 81defc6a 1b3541d5
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -241,11 +241,11 @@ public final class AudioAttributes implements Parcelable {

    /**
     * @hide
     * CANDIDATE FOR PUBLIC API
     * Return the capture preset.
     * @return one of the values that can be set in {@link Builder#setCapturePreset(int)} or a
     *    negative value if none has been set.
     */
    @SystemApi
    public int getCapturePreset() {
        return mSource;
    }
@@ -508,6 +508,7 @@ public final class AudioAttributes implements Parcelable {
         *     {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}.
         * @return the same Builder instance.
         */
        @SystemApi
        public Builder setCapturePreset(int preset) {
            switch (preset) {
                case MediaRecorder.AudioSource.DEFAULT:
+8 −6
Original line number Diff line number Diff line
@@ -2459,6 +2459,7 @@ public class AudioManager {
     *     {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
     * @throws IllegalArgumentException
     */
    @SystemApi
    public int requestAudioFocus(OnAudioFocusChangeListener l,
            @NonNull AudioAttributes requestAttributes,
            int durationHint,
@@ -2853,17 +2854,17 @@ public class AudioManager {

    /**
     * @hide
     * CANDIDATE FOR PUBLIC API
     * Register the given {@link AudioPolicy}.
     * This call is synchronous and blocks until the registration process successfully completed
     * or failed to complete.
     * @param policy the {@link AudioPolicy} to register.
     * @param policy the non-null {@link AudioPolicy} to register.
     * @return {@link #ERROR} if there was an error communicating with the registration service
     *    or if the user doesn't have the required
     *    {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} permission,
     *    {@link #SUCCESS} otherwise.
     */
    public int registerAudioPolicy(AudioPolicy policy) {
    @SystemApi
    public int registerAudioPolicy(@NonNull AudioPolicy policy) {
        if (policy == null) {
            throw new IllegalArgumentException("Illegal null AudioPolicy argument");
        }
@@ -2885,16 +2886,17 @@ public class AudioManager {

    /**
     * @hide
     * CANDIDATE FOR PUBLIC API
     * @param policy the {@link AudioPolicy} to unregister.
     * @param policy the non-null {@link AudioPolicy} to unregister.
     */
    public void unregisterAudioPolicyAsync(AudioPolicy policy) {
    @SystemApi
    public void unregisterAudioPolicyAsync(@NonNull AudioPolicy policy) {
        if (policy == null) {
            throw new IllegalArgumentException("Illegal null AudioPolicy argument");
        }
        IAudioService service = getService();
        try {
            service.unregisterAudioPolicyAsync(policy.token());
            policy.setRegistration(null);
        } catch (RemoteException e) {
            Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e);
        }
+23 −6
Original line number Diff line number Diff line
@@ -5538,6 +5538,8 @@ public class AudioService extends IAudioService.Stub {
        pw.print("  mMusicActiveMs="); pw.println(mMusicActiveMs);
        pw.print("  mMcc="); pw.println(mMcc);
        pw.print("  mHasVibrator="); pw.println(mHasVibrator);

        dumpAudioPolicies(pw);
    }

    private static String safeMediaVolumeStateToString(Integer state) {
@@ -5797,6 +5799,10 @@ public class AudioService extends IAudioService.Stub {
        }
        synchronized (mAudioPolicies) {
            try {
                if (mAudioPolicies.containsKey(cb)) {
                    Slog.e(TAG, "Cannot re-register policy");
                    return null;
                }
                AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
                cb.linkToDeath(app, 0/*flags*/);
                regId = app.connectMixes();
@@ -5817,6 +5823,7 @@ public class AudioService extends IAudioService.Stub {
            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*/);
            }
@@ -5825,12 +5832,21 @@ public class AudioService extends IAudioService.Stub {
        // TODO implement clearing mix attribute matching info in native audio policy
    }

    private void dumpAudioPolicies(PrintWriter pw) {
        pw.println("\nAudio policies:");
        synchronized (mAudioPolicies) {
            for(AudioPolicyProxy policy : mAudioPolicies.values()) {
                pw.println(policy.toLogFriendlyString());
            }
        }
    }

    //======================
    // Audio policy proxy
    //======================
    /**
     * This internal class inherits from AudioPolicyConfig which contains all the mixes and
     * their configurations.
     * This internal class inherits from AudioPolicyConfig, each instance contains all the
     * mixes of an AudioPolicy and their configurations.
     */
    public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient {
        private static final String TAG = "AudioPolicyProxy";
@@ -5838,7 +5854,7 @@ public class AudioService extends IAudioService.Stub {
        IBinder mToken;
        AudioPolicyProxy(AudioPolicyConfig config, IBinder token) {
            super(config);
            setRegistration(new String(config.toString() + ":ap:" + mAudioPolicyCounter++));
            setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++));
            mToken = token;
        }

@@ -5852,7 +5868,7 @@ public class AudioService extends IAudioService.Stub {

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

        void disconnectMixes() {
@@ -5863,8 +5879,9 @@ public class AudioService extends IAudioService.Stub {
            for (AudioMix mix : mMixes) {
                // TODO implement sending the mix attribute matching info to native audio policy
                if (DEBUG_AP) {
                    Log.v(TAG, "AudioPolicyProxy connect mix state=" + connectionState
                            + " addr=" + mix.getRegistration()); }
                    Log.v(TAG, "AudioPolicyProxy mix new connection state=" + connectionState
                            + " addr=" + mix.getRegistration());
                }
                AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_REMOTE_SUBMIX,
                        connectionState,
                        mix.getRegistration());
+40 −1
Original line number Diff line number Diff line
@@ -17,21 +17,25 @@
package android.media.audiopolicy;

import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.media.AudioFormat;
import android.media.AudioSystem;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;

/**
 * @hide
 */
@SystemApi
public class AudioMix {

    private AudioMixingRule mRule;
    private AudioFormat mFormat;
    private int mRouteFlags;
    private String mRegistrationId;
    private int mMixType = MIX_TYPE_INVALID;

    /**
     * All parameters are guaranteed valid through the Builder.
@@ -41,20 +45,39 @@ public class AudioMix {
        mFormat = format;
        mRouteFlags = routeFlags;
        mRegistrationId = null;
        mMixType = rule.getTargetMixType();
    }

    /**
     * An audio mix behavior where the output of the mix is sent to the original destination of
     * the audio signal, i.e. an output device for an output mix, or a recording for an input mix.
     */
    @SystemApi
    public static final int ROUTE_FLAG_RENDER    = 0x1;
    /**
     * An audio mix behavior where the output of the mix is rerouted back to the framework and
     * is accessible for injection or capture through the {@link Audiotrack} and {@link AudioRecord}
     * is accessible for injection or capture through the {@link AudioTrack} and {@link AudioRecord}
     * APIs.
     */
    @SystemApi
    public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;

    /**
     * @hide
     * Invalid mix type, default value.
     */
    public static final int MIX_TYPE_INVALID = -1;
    /**
     * @hide
     * Mix type indicating playback streams are mixed.
     */
    public static final int MIX_TYPE_PLAYERS = 0;
    /**
     * @hide
     * Mix type indicating recording streams are mixed.
     */
    public static final int MIX_TYPE_RECORDERS = 1;

    int getRouteFlags() {
        return mRouteFlags;
    }
@@ -67,6 +90,11 @@ public class AudioMix {
        return mRule;
    }

    /** @hide */
    public int getMixType() {
        return mMixType;
    }

    void setRegistration(String regId) {
        mRegistrationId = regId;
    }
@@ -76,6 +104,12 @@ public class AudioMix {
        return mRegistrationId;
    }

    /** @hide */
    @Override
    public int hashCode() {
        return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
    }

    /** @hide */
    @IntDef(flag = true,
            value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } )
@@ -86,6 +120,7 @@ public class AudioMix {
     * Builder class for {@link AudioMix} objects
     *
     */
    @SystemApi
    public static class Builder {
        private AudioMixingRule mRule = null;
        private AudioFormat mFormat = null;
@@ -102,6 +137,7 @@ public class AudioMix {
         * @param rule a non-null {@link AudioMixingRule} instance.
         * @throws IllegalArgumentException
         */
        @SystemApi
        public Builder(AudioMixingRule rule)
                throws IllegalArgumentException {
            if (rule == null) {
@@ -132,6 +168,7 @@ public class AudioMix {
         * @return the same Builder instance.
         * @throws IllegalArgumentException
         */
        @SystemApi
        public Builder setFormat(AudioFormat format)
                throws IllegalArgumentException {
            if (format == null) {
@@ -148,6 +185,7 @@ public class AudioMix {
         * @return the same Builder instance.
         * @throws IllegalArgumentException
         */
        @SystemApi
        public Builder setRouteFlags(@RouteFlags int routeFlags)
                throws IllegalArgumentException {
            if (routeFlags == 0) {
@@ -166,6 +204,7 @@ public class AudioMix {
         * @return a new {@link AudioMix} object
         * @throws IllegalArgumentException if no {@link AudioMixingRule} has been set.
         */
        @SystemApi
        public AudioMix build() throws IllegalArgumentException {
            if (mRule == null) {
                throw new IllegalArgumentException("Illegal null AudioMixingRule");
+177 −10
Original line number Diff line number Diff line
@@ -16,10 +16,13 @@

package android.media.audiopolicy;

import android.annotation.SystemApi;
import android.media.AudioAttributes;
import android.os.Parcel;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;


/**
@@ -35,44 +38,114 @@ import java.util.Iterator;
 *         .build();
 * </pre>
 */
@SystemApi
public class AudioMixingRule {

    private AudioMixingRule(ArrayList<AttributeMatchCriterion> criteria) {
    private AudioMixingRule(int mixType, ArrayList<AttributeMatchCriterion> criteria) {
        mCriteria = criteria;
        mTargetMixType = mixType;
    }

    /**
     * A rule requiring the usage information of the {@link AudioAttributes} to match
     * A rule requiring the usage information of the {@link AudioAttributes} to match.
     */
    @SystemApi
    public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1;
    /**
     * A rule requiring the usage information of the {@link AudioAttributes} to differ
     * A rule requiring the capture preset information of the {@link AudioAttributes} to match.
     */
    public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE = 0x1 << 1;
    @SystemApi
    public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1;

    private final static int RULE_EXCLUSION_MASK = 0x8000;
    /**
     * @hide
     * A rule requiring the usage information of the {@link AudioAttributes} to differ.
     */
    public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE =
            RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_USAGE;
    /**
     * @hide
     * A rule requiring the capture preset information of the {@link AudioAttributes} to differ.
     */
    public static final int RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET =
            RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;

    static final class AttributeMatchCriterion {
        AudioAttributes mAttr;
        int mRule;

        /** input parameters must be valid */
        AttributeMatchCriterion(AudioAttributes attributes, int rule) {
            mAttr = attributes;
            mRule = rule;
        }

        @Override
        public int hashCode() {
            return Objects.hash(mAttr, mRule);
        }

    private ArrayList<AttributeMatchCriterion> mCriteria;
        void writeToParcel(Parcel dest) {
            dest.writeInt(mRule);
            if ((mRule == RULE_MATCH_ATTRIBUTE_USAGE) || (mRule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
                dest.writeInt(mAttr.getUsage());
            } else {
                // capture preset rule
                dest.writeInt(mAttr.getCapturePreset());
            }
        }
    }

    private final int mTargetMixType;
    int getTargetMixType() { return mTargetMixType; }
    private final ArrayList<AttributeMatchCriterion> mCriteria;
    ArrayList<AttributeMatchCriterion> getCriteria() { return mCriteria; }

    @Override
    public int hashCode() {
        return Objects.hash(mTargetMixType, mCriteria);
    }

    private static boolean isValidSystemApiRule(int rule) {
        switch(rule) {
            case RULE_MATCH_ATTRIBUTE_USAGE:
            case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
                return true;
            default:
                return false;
        }
    }

    private static boolean isValidIntRule(int rule) {
        switch(rule) {
            case RULE_MATCH_ATTRIBUTE_USAGE:
            case RULE_EXCLUDE_ATTRIBUTE_USAGE:
            case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
            case RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
                return true;
            default:
                return false;
        }
    }

    private static boolean isPlayerRule(int rule) {
        return ((rule == RULE_MATCH_ATTRIBUTE_USAGE)
                || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE));
    }

    /**
     * Builder class for {@link AudioMixingRule} objects
     *
     */
    @SystemApi
    public static class Builder {
        private ArrayList<AttributeMatchCriterion> mCriteria;
        private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;

        /**
         * Constructs a new Builder with no rules.
         */
        @SystemApi
        public Builder() {
            mCriteria = new ArrayList<AttributeMatchCriterion>();
        }
@@ -81,18 +154,80 @@ public class AudioMixingRule {
         * Add a rule for the selection of which streams are mixed together.
         * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
         *     rule hasn't been set yet.
         * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}.
         * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
         * @return the same Builder instance.
         * @throws IllegalArgumentException
         */
        @SystemApi
        public Builder addRule(AudioAttributes attrToMatch, int rule)
                throws IllegalArgumentException {
            if (!isValidSystemApiRule(rule)) {
                throw new IllegalArgumentException("Illegal rule value " + rule);
            }
            return addRuleInt(attrToMatch, rule);
        }

        /**
         * Add a rule by exclusion for the selection of which streams are mixed together.
         * <br>For instance the following code
         * <br><pre>
         * AudioAttributes mediaAttr = new AudioAttributes.Builder()
         *         .setUsage(AudioAttributes.USAGE_MEDIA)
         *         .build();
         * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
         *         .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
         *         .build();
         * </pre>
         * <br>will create a rule which maps to any usage value, except USAGE_MEDIA.
         * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
         *     rule hasn't been set yet.
         * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
         * @return the same Builder instance.
         * @throws IllegalArgumentException
         */
        @SystemApi
        public Builder excludeRule(AudioAttributes attrToMatch, int rule)
                throws IllegalArgumentException {
            if (!isValidSystemApiRule(rule)) {
                throw new IllegalArgumentException("Illegal rule value " + rule);
            }
            return addRuleInt(attrToMatch, rule | RULE_EXCLUSION_MASK);
        }

        /**
         * Add or exclude a rule for the selection of which streams are mixed together.
         * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
         *     rule hasn't been set yet.
         * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
         *     {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET}.
         * @return the same Builder instance.
         * @throws IllegalArgumentException
         */
        Builder addRuleInt(AudioAttributes attrToMatch, int rule)
                throws IllegalArgumentException {
            if (attrToMatch == null) {
                throw new IllegalArgumentException("Illegal null AudioAttributes argument");
            }
            if ((rule != RULE_MATCH_ATTRIBUTE_USAGE) && (rule != RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
            if (!isValidIntRule(rule)) {
                throw new IllegalArgumentException("Illegal rule value " + rule);
            } else {
                // as rules are added to the Builder, we verify they are consistent with the type
                // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
                if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
                    if (isPlayerRule(rule)) {
                        mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
                    } else {
                        mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
                    }
                } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule))
                        || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule)))
                {
                    throw new IllegalArgumentException("Incompatible rule for mix");
                }
            }
            synchronized (mCriteria) {
                Iterator<AttributeMatchCriterion> crIterator = mCriteria.iterator();
@@ -111,6 +246,19 @@ public class AudioMixingRule {
                                        + attrToMatch);
                            }
                        }
                    } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET)
                            || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) {
                        // "capture preset"-base rule
                        if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) {
                            if (criterion.mRule == rule) {
                             // rule already exists, we're done
                                return this;
                            } else {
                                // criterion already exists with a another rule, it is incompatible
                                throw new IllegalArgumentException("Contradictory rule exists for "
                                        + attrToMatch);
                            }
                        }
                    }
                }
                // rule didn't exist, add it
@@ -119,13 +267,32 @@ public class AudioMixingRule {
            return this;
        }

        Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException {
            int rule = in.readInt();
            AudioAttributes attr;
            if ((rule == RULE_MATCH_ATTRIBUTE_USAGE) || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
                int usage = in.readInt();
                attr = new AudioAttributes.Builder()
                        .setUsage(usage).build();
            } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET)
                    || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) {
                int preset = in.readInt();
                attr = new AudioAttributes.Builder()
                        .setInternalCapturePreset(preset).build();
            } else {
                in.readInt(); // assume there was in int value to read as for now they come in pair
                throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel");
            }
            return addRuleInt(attr, rule);
        }

        /**
         * Combines all of the matching and exclusion rules that have been set and return a new
         * {@link AudioMixingRule} object.
         * @return a new {@link AudioMixingRule} object
         */
        public AudioMixingRule build() {
            return new AudioMixingRule(mCriteria);
            return new AudioMixingRule(mTargetMixType, mCriteria);
        }
    }
}
Loading