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

Commit b773bf85 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Fix voice communication audio playback capture"

parents 70c57a49 24e0298b
Loading
Loading
Loading
Loading
+6 −0
Original line number Original line Diff line number Diff line
@@ -147,6 +147,7 @@ static jclass gAudioMixingRuleClass;
static struct {
static struct {
    jfieldID    mCriteria;
    jfieldID    mCriteria;
    jfieldID    mAllowPrivilegedPlaybackCapture;
    jfieldID    mAllowPrivilegedPlaybackCapture;
    jfieldID    mVoiceCommunicationCaptureAllowed;
    // other fields unused by JNI
    // other fields unused by JNI
} gAudioMixingRuleFields;
} gAudioMixingRuleFields;


@@ -1919,6 +1920,8 @@ static jint convertAudioMixToNative(JNIEnv *env,
    jobject jRuleCriteria = env->GetObjectField(jRule, gAudioMixingRuleFields.mCriteria);
    jobject jRuleCriteria = env->GetObjectField(jRule, gAudioMixingRuleFields.mCriteria);
    nAudioMix->mAllowPrivilegedPlaybackCapture =
    nAudioMix->mAllowPrivilegedPlaybackCapture =
            env->GetBooleanField(jRule, gAudioMixingRuleFields.mAllowPrivilegedPlaybackCapture);
            env->GetBooleanField(jRule, gAudioMixingRuleFields.mAllowPrivilegedPlaybackCapture);
    nAudioMix->mVoiceCommunicationCaptureAllowed =
            env->GetBooleanField(jRule, gAudioMixingRuleFields.mVoiceCommunicationCaptureAllowed);
    env->DeleteLocalRef(jRule);
    env->DeleteLocalRef(jRule);
    jobjectArray jCriteria = (jobjectArray)env->CallObjectMethod(jRuleCriteria,
    jobjectArray jCriteria = (jobjectArray)env->CallObjectMethod(jRuleCriteria,
                                                                 gArrayListMethods.toArray);
                                                                 gArrayListMethods.toArray);
@@ -2682,6 +2685,9 @@ int register_android_media_AudioSystem(JNIEnv *env)
    gAudioMixingRuleFields.mAllowPrivilegedPlaybackCapture =
    gAudioMixingRuleFields.mAllowPrivilegedPlaybackCapture =
            GetFieldIDOrDie(env, audioMixingRuleClass, "mAllowPrivilegedPlaybackCapture", "Z");
            GetFieldIDOrDie(env, audioMixingRuleClass, "mAllowPrivilegedPlaybackCapture", "Z");


    gAudioMixingRuleFields.mVoiceCommunicationCaptureAllowed =
            GetFieldIDOrDie(env, audioMixingRuleClass, "mVoiceCommunicationCaptureAllowed", "Z");

    jclass audioMixMatchCriterionClass =
    jclass audioMixMatchCriterionClass =
                FindClassOrDie(env, "android/media/audiopolicy/AudioMixingRule$AudioMixMatchCriterion");
                FindClassOrDie(env, "android/media/audiopolicy/AudioMixingRule$AudioMixMatchCriterion");
    gAudioMixMatchCriterionClass = MakeGlobalRefOrDie(env,audioMixMatchCriterionClass);
    gAudioMixMatchCriterionClass = MakeGlobalRefOrDie(env,audioMixMatchCriterionClass);
+10 −0
Original line number Original line Diff line number Diff line
@@ -192,6 +192,16 @@ public class AudioMix {
        return mRule.isAffectingUsage(usage);
        return mRule.isAffectingUsage(usage);
    }
    }


    /**
      * Returns {@code true} if the rule associated with this mix contains a
      * RULE_MATCH_ATTRIBUTE_USAGE criterion for the given usage
      *
      * @hide
      */
    public boolean containsMatchAttributeRuleForUsage(int usage) {
        return mRule.containsMatchAttributeRuleForUsage(usage);
    }

    /** @hide */
    /** @hide */
    public boolean isRoutedToDevice(int deviceType, @NonNull String deviceAddress) {
    public boolean isRoutedToDevice(int deviceType, @NonNull String deviceAddress) {
        if ((mRouteFlags & ROUTE_FLAG_RENDER) != ROUTE_FLAG_RENDER) {
        if ((mRouteFlags & ROUTE_FLAG_RENDER) != ROUTE_FLAG_RENDER) {
+61 −4
Original line number Original line Diff line number Diff line
@@ -47,10 +47,12 @@ import java.util.Objects;
public class AudioMixingRule {
public class AudioMixingRule {


    private AudioMixingRule(int mixType, ArrayList<AudioMixMatchCriterion> criteria,
    private AudioMixingRule(int mixType, ArrayList<AudioMixMatchCriterion> criteria,
                            boolean allowPrivilegedPlaybackCapture) {
                            boolean allowPrivilegedPlaybackCapture,
                            boolean voiceCommunicationCaptureAllowed) {
        mCriteria = criteria;
        mCriteria = criteria;
        mTargetMixType = mixType;
        mTargetMixType = mixType;
        mAllowPrivilegedPlaybackCapture = allowPrivilegedPlaybackCapture;
        mAllowPrivilegedPlaybackCapture = allowPrivilegedPlaybackCapture;
        mVoiceCommunicationCaptureAllowed = voiceCommunicationCaptureAllowed;
    }
    }


    /**
    /**
@@ -171,6 +173,23 @@ public class AudioMixingRule {
        return false;
        return false;
    }
    }


    /**
      * Returns {@code true} if this rule contains a RULE_MATCH_ATTRIBUTE_USAGE criterion for
      * the given usage
      *
      * @hide
      */
    boolean containsMatchAttributeRuleForUsage(int usage) {
        for (AudioMixMatchCriterion criterion : mCriteria) {
            if (criterion.mRule == RULE_MATCH_ATTRIBUTE_USAGE
                    && criterion.mAttr != null
                    && criterion.mAttr.getUsage() == usage) {
                return true;
            }
        }
        return false;
    }

    private static boolean areCriteriaEquivalent(ArrayList<AudioMixMatchCriterion> cr1,
    private static boolean areCriteriaEquivalent(ArrayList<AudioMixMatchCriterion> cr1,
            ArrayList<AudioMixMatchCriterion> cr2) {
            ArrayList<AudioMixMatchCriterion> cr2) {
        if (cr1 == null || cr2 == null) return false;
        if (cr1 == null || cr2 == null) return false;
@@ -188,12 +207,24 @@ public class AudioMixingRule {
    public ArrayList<AudioMixMatchCriterion> getCriteria() { return mCriteria; }
    public ArrayList<AudioMixMatchCriterion> getCriteria() { return mCriteria; }
    @UnsupportedAppUsage
    @UnsupportedAppUsage
    private boolean mAllowPrivilegedPlaybackCapture = false;
    private boolean mAllowPrivilegedPlaybackCapture = false;
    @UnsupportedAppUsage
    private boolean mVoiceCommunicationCaptureAllowed = false;


    /** @hide */
    /** @hide */
    public boolean allowPrivilegedPlaybackCapture() {
    public boolean allowPrivilegedPlaybackCapture() {
        return mAllowPrivilegedPlaybackCapture;
        return mAllowPrivilegedPlaybackCapture;
    }
    }


    /** @hide */
    public boolean voiceCommunicationCaptureAllowed() {
        return mVoiceCommunicationCaptureAllowed;
    }

    /** @hide */
    public void setVoiceCommunicationCaptureAllowed(boolean allowed) {
        mVoiceCommunicationCaptureAllowed = allowed;
    }

    /** @hide */
    /** @hide */
    @Override
    @Override
    public boolean equals(Object o) {
    public boolean equals(Object o) {
@@ -203,12 +234,18 @@ public class AudioMixingRule {
        final AudioMixingRule that = (AudioMixingRule) o;
        final AudioMixingRule that = (AudioMixingRule) o;
        return (this.mTargetMixType == that.mTargetMixType)
        return (this.mTargetMixType == that.mTargetMixType)
                && (areCriteriaEquivalent(this.mCriteria, that.mCriteria)
                && (areCriteriaEquivalent(this.mCriteria, that.mCriteria)
                && this.mAllowPrivilegedPlaybackCapture == that.mAllowPrivilegedPlaybackCapture);
                && this.mAllowPrivilegedPlaybackCapture == that.mAllowPrivilegedPlaybackCapture
                && this.mVoiceCommunicationCaptureAllowed
                    == that.mVoiceCommunicationCaptureAllowed);
    }
    }


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


    private static boolean isValidSystemApiRule(int rule) {
    private static boolean isValidSystemApiRule(int rule) {
@@ -276,6 +313,8 @@ public class AudioMixingRule {
        private ArrayList<AudioMixMatchCriterion> mCriteria;
        private ArrayList<AudioMixMatchCriterion> mCriteria;
        private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
        private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
        private boolean mAllowPrivilegedPlaybackCapture = false;
        private boolean mAllowPrivilegedPlaybackCapture = false;
        // This value should be set internally according to a permission check
        private boolean mVoiceCommunicationCaptureAllowed = false;


        /**
        /**
         * Constructs a new Builder with no rules.
         * Constructs a new Builder with no rules.
@@ -400,6 +439,23 @@ public class AudioMixingRule {
            return this;
            return this;
        }
        }


        /**
         * Set if the caller of the rule is able to capture voice communication output.
         * A system app can capture voice communication output only if it is granted with the.
         * CAPTURE_VOICE_COMMUNICATION_OUTPUT permission.
         *
         * Note that this method is for internal use only and should not be called by the app that
         * creates the rule.
         *
         * @return the same Builder instance.
         *
         * @hide
         */
        public @NonNull Builder voiceCommunicationCaptureAllowed(boolean allowed) {
            mVoiceCommunicationCaptureAllowed = allowed;
            return this;
        }

        /**
        /**
         * Add or exclude a rule for the selection of which streams are mixed together.
         * Add or exclude a rule for the selection of which streams are mixed together.
         * Does error checking on the parameters.
         * Does error checking on the parameters.
@@ -583,7 +639,8 @@ public class AudioMixingRule {
         * @return a new {@link AudioMixingRule} object
         * @return a new {@link AudioMixingRule} object
         */
         */
        public AudioMixingRule build() {
        public AudioMixingRule build() {
            return new AudioMixingRule(mTargetMixType, mCriteria, mAllowPrivilegedPlaybackCapture);
            return new AudioMixingRule(mTargetMixType, mCriteria,
                mAllowPrivilegedPlaybackCapture, mVoiceCommunicationCaptureAllowed);
        }
        }
    }
    }
}
}
+7 −1
Original line number Original line Diff line number Diff line
@@ -98,6 +98,8 @@ public class AudioPolicyConfig implements Parcelable {
            dest.writeInt(mix.getFormat().getChannelMask());
            dest.writeInt(mix.getFormat().getChannelMask());
            // write opt-out respect
            // write opt-out respect
            dest.writeBoolean(mix.getRule().allowPrivilegedPlaybackCapture());
            dest.writeBoolean(mix.getRule().allowPrivilegedPlaybackCapture());
            // write voice communication capture allowed flag
            dest.writeBoolean(mix.getRule().voiceCommunicationCaptureAllowed());
            // write mix rules
            // write mix rules
            final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
            final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
            dest.writeInt(criteria.size());
            dest.writeInt(criteria.size());
@@ -128,8 +130,10 @@ public class AudioPolicyConfig implements Parcelable {
            mixBuilder.setFormat(format);
            mixBuilder.setFormat(format);


            AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
            AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
            // write opt-out respect
            // read opt-out respect
            ruleBuilder.allowPrivilegedPlaybackCapture(in.readBoolean());
            ruleBuilder.allowPrivilegedPlaybackCapture(in.readBoolean());
            // read voice capture allowed flag
            ruleBuilder.voiceCommunicationCaptureAllowed(in.readBoolean());
            // read mix rules
            // read mix rules
            int nbRules = in.readInt();
            int nbRules = in.readInt();
            for (int j = 0 ; j < nbRules ; j++) {
            for (int j = 0 ; j < nbRules ; j++) {
@@ -169,6 +173,8 @@ public class AudioPolicyConfig implements Parcelable {
            textDump += Integer.toHexString(mix.getFormat().getChannelMask()).toUpperCase() + "\n";
            textDump += Integer.toHexString(mix.getFormat().getChannelMask()).toUpperCase() + "\n";
            textDump += "  ignore playback capture opt out="
            textDump += "  ignore playback capture opt out="
                    + mix.getRule().allowPrivilegedPlaybackCapture() + "\n";
                    + mix.getRule().allowPrivilegedPlaybackCapture() + "\n";
            textDump += "  allow voice communication capture="
                    + mix.getRule().voiceCommunicationCaptureAllowed() + "\n";
            // write mix rules
            // write mix rules
            final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
            final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
            for (AudioMixMatchCriterion criterion : criteria) {
            for (AudioMixMatchCriterion criterion : criteria) {
+29 −53
Original line number Original line Diff line number Diff line
@@ -21,7 +21,6 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static android.media.AudioManager.STREAM_SYSTEM;
import static android.media.AudioManager.STREAM_SYSTEM;
import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
@@ -91,7 +90,6 @@ import android.media.PlayerBase;
import android.media.VolumePolicy;
import android.media.VolumePolicy;
import android.media.audiofx.AudioEffect;
import android.media.audiofx.AudioEffect;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicyConfig;
import android.media.audiopolicy.AudioPolicyConfig;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioProductStrategy;
@@ -6826,8 +6824,9 @@ public class AudioService extends IAudioService.Stub


        boolean requireValidProjection = false;
        boolean requireValidProjection = false;
        boolean requireCaptureAudioOrMediaOutputPerm = false;
        boolean requireCaptureAudioOrMediaOutputPerm = false;
        boolean requireVoiceComunicationOutputPerm = false;
        boolean requireModifyRouting = false;
        boolean requireModifyRouting = false;
        ArrayList<AudioMix> voiceCommunicationCaptureMixes = null;



        if (hasFocusAccess || isVolumeController) {
        if (hasFocusAccess || isVolumeController) {
            requireModifyRouting |= true;
            requireModifyRouting |= true;
@@ -6836,23 +6835,29 @@ public class AudioService extends IAudioService.Stub
            requireModifyRouting |= true;
            requireModifyRouting |= true;
        }
        }
        for (AudioMix mix : policyConfig.getMixes()) {
        for (AudioMix mix : policyConfig.getMixes()) {
            // If mix is trying to capture USAGE_VOICE_COMMUNICATION using playback capture
            // If mix is requesting privileged capture
            if (isVoiceCommunicationPlaybackCaptureMix(mix)) {
            if (mix.getRule().allowPrivilegedPlaybackCapture()) {
                // then it must have CAPTURE_USAGE_VOICE_COMMUNICATION_OUTPUT permission
                requireVoiceComunicationOutputPerm |= true;
            }
            // If mix is requesting privileged capture and is capturing at
            // least one usage which is not USAGE_VOICE_COMMUNICATION.
            if (mix.getRule().allowPrivilegedPlaybackCapture()
                    && isNonVoiceCommunicationCaptureMix(mix)) {
                // then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission
                // then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission
                requireCaptureAudioOrMediaOutputPerm |= true;
                requireCaptureAudioOrMediaOutputPerm |= true;

                // and its format must be low quality enough
                // and its format must be low quality enough
                String error = mix.canBeUsedForPrivilegedCapture(mix.getFormat());
                String error = mix.canBeUsedForPrivilegedCapture(mix.getFormat());
                if (error != null) {
                if (error != null) {
                    Log.e(TAG, error);
                    Log.e(TAG, error);
                    return false;
                    return false;
                }
                }

                // If mix is trying to excplicitly capture USAGE_VOICE_COMMUNICATION
                if (mix.containsMatchAttributeRuleForUsage(
                        AudioAttributes.USAGE_VOICE_COMMUNICATION)) {
                    // then it must have CAPTURE_USAGE_VOICE_COMMUNICATION_OUTPUT permission
                    // Note that for UID, USERID or EXCLDUE rules, the capture will be silenced
                    // in AudioPolicyMix
                    if (voiceCommunicationCaptureMixes == null) {
                        voiceCommunicationCaptureMixes = new ArrayList<AudioMix>();
                    }
                    voiceCommunicationCaptureMixes.add(mix);
                }
            }
            }


            // If mix is RENDER|LOOPBACK, then an audio MediaProjection is enough
            // If mix is RENDER|LOOPBACK, then an audio MediaProjection is enough
@@ -6872,14 +6877,20 @@ public class AudioService extends IAudioService.Stub
            return false;
            return false;
        }
        }


        if (requireVoiceComunicationOutputPerm
        if (voiceCommunicationCaptureMixes != null && voiceCommunicationCaptureMixes.size() > 0) {
                && !callerHasPermission(
            if (!callerHasPermission(
                    android.Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT)) {
                    android.Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT)) {
                Log.e(TAG, "Privileged audio capture for voice communication requires "
                Log.e(TAG, "Privileged audio capture for voice communication requires "
                        + "CAPTURE_VOICE_COMMUNICATION_OUTPUT system permission");
                        + "CAPTURE_VOICE_COMMUNICATION_OUTPUT system permission");
                return false;
                return false;
            }
            }


            // If permission check succeeded, we set the flag in each of the mixing rules
            for (AudioMix mix : voiceCommunicationCaptureMixes) {
                mix.getRule().setVoiceCommunicationCaptureAllowed(true);
            }
        }

        if (requireValidProjection && !canProjectAudio(projection)) {
        if (requireValidProjection && !canProjectAudio(projection)) {
            return false;
            return false;
        }
        }
@@ -6893,41 +6904,6 @@ public class AudioService extends IAudioService.Stub
        return true;
        return true;
    }
    }


    /**
    * Checks whether a given AudioMix is used for playback capture
    * (has the ROUTE_FLAG_LOOP_BACK_RENDER flag) and has a matching
    * criterion for USAGE_VOICE_COMMUNICATION.
    */
    private boolean isVoiceCommunicationPlaybackCaptureMix(AudioMix mix) {
        if (mix.getRouteFlags() != mix.ROUTE_FLAG_LOOP_BACK_RENDER) {
            return false;
        }

        for (AudioMixMatchCriterion criterion : mix.getRule().getCriteria()) {
            if (criterion.getRule() == RULE_MATCH_ATTRIBUTE_USAGE
                    && criterion.getAudioAttributes().getUsage()
                    == AudioAttributes.USAGE_VOICE_COMMUNICATION) {
                return true;
            }
        }
        return false;
    }

    /**
    * Checks whether a given AudioMix has a matching
    * criterion for a usage which is not USAGE_VOICE_COMMUNICATION.
    */
    private boolean isNonVoiceCommunicationCaptureMix(AudioMix mix) {
        for (AudioMixMatchCriterion criterion : mix.getRule().getCriteria()) {
            if (criterion.getRule() == RULE_MATCH_ATTRIBUTE_USAGE
                    && criterion.getAudioAttributes().getUsage()
                    != AudioAttributes.USAGE_VOICE_COMMUNICATION) {
                return true;
            }
        }
        return false;
    }

    private boolean callerHasPermission(String permission) {
    private boolean callerHasPermission(String permission) {
        return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
        return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
    }
    }