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 Diff line number Diff line
@@ -147,6 +147,7 @@ static jclass gAudioMixingRuleClass;
static struct {
    jfieldID    mCriteria;
    jfieldID    mAllowPrivilegedPlaybackCapture;
    jfieldID    mVoiceCommunicationCaptureAllowed;
    // other fields unused by JNI
} gAudioMixingRuleFields;

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

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

    jclass audioMixMatchCriterionClass =
                FindClassOrDie(env, "android/media/audiopolicy/AudioMixingRule$AudioMixMatchCriterion");
    gAudioMixMatchCriterionClass = MakeGlobalRefOrDie(env,audioMixMatchCriterionClass);
+10 −0
Original line number Diff line number Diff line
@@ -192,6 +192,16 @@ public class AudioMix {
        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 */
    public boolean isRoutedToDevice(int deviceType, @NonNull String deviceAddress) {
        if ((mRouteFlags & ROUTE_FLAG_RENDER) != ROUTE_FLAG_RENDER) {
+61 −4
Original line number Diff line number Diff line
@@ -47,10 +47,12 @@ import java.util.Objects;
public class AudioMixingRule {

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

    /**
@@ -171,6 +173,23 @@ public class AudioMixingRule {
        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,
            ArrayList<AudioMixMatchCriterion> cr2) {
        if (cr1 == null || cr2 == null) return false;
@@ -188,12 +207,24 @@ public class AudioMixingRule {
    public ArrayList<AudioMixMatchCriterion> getCriteria() { return mCriteria; }
    @UnsupportedAppUsage
    private boolean mAllowPrivilegedPlaybackCapture = false;
    @UnsupportedAppUsage
    private boolean mVoiceCommunicationCaptureAllowed = false;

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

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

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

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

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

    private static boolean isValidSystemApiRule(int rule) {
@@ -276,6 +313,8 @@ public class AudioMixingRule {
        private ArrayList<AudioMixMatchCriterion> mCriteria;
        private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
        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.
@@ -400,6 +439,23 @@ public class AudioMixingRule {
            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.
         * Does error checking on the parameters.
@@ -583,7 +639,8 @@ public class AudioMixingRule {
         * @return a new {@link AudioMixingRule} object
         */
        public AudioMixingRule build() {
            return new AudioMixingRule(mTargetMixType, mCriteria, mAllowPrivilegedPlaybackCapture);
            return new AudioMixingRule(mTargetMixType, mCriteria,
                mAllowPrivilegedPlaybackCapture, mVoiceCommunicationCaptureAllowed);
        }
    }
}
+7 −1
Original line number Diff line number Diff line
@@ -98,6 +98,8 @@ public class AudioPolicyConfig implements Parcelable {
            dest.writeInt(mix.getFormat().getChannelMask());
            // write opt-out respect
            dest.writeBoolean(mix.getRule().allowPrivilegedPlaybackCapture());
            // write voice communication capture allowed flag
            dest.writeBoolean(mix.getRule().voiceCommunicationCaptureAllowed());
            // write mix rules
            final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
            dest.writeInt(criteria.size());
@@ -128,8 +130,10 @@ public class AudioPolicyConfig implements Parcelable {
            mixBuilder.setFormat(format);

            AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
            // write opt-out respect
            // read opt-out respect
            ruleBuilder.allowPrivilegedPlaybackCapture(in.readBoolean());
            // read voice capture allowed flag
            ruleBuilder.voiceCommunicationCaptureAllowed(in.readBoolean());
            // read mix rules
            int nbRules = in.readInt();
            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 += "  ignore playback capture opt out="
                    + mix.getRule().allowPrivilegedPlaybackCapture() + "\n";
            textDump += "  allow voice communication capture="
                    + mix.getRule().voiceCommunicationCaptureAllowed() + "\n";
            // write mix rules
            final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
            for (AudioMixMatchCriterion criterion : criteria) {
+29 −53
Original line number 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_VIBRATE;
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.provider.Settings.Secure.VOLUME_HUSH_MUTE;
import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
@@ -91,7 +90,6 @@ import android.media.PlayerBase;
import android.media.VolumePolicy;
import android.media.audiofx.AudioEffect;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicyConfig;
import android.media.audiopolicy.AudioProductStrategy;
@@ -6826,8 +6824,9 @@ public class AudioService extends IAudioService.Stub

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


        if (hasFocusAccess || isVolumeController) {
            requireModifyRouting |= true;
@@ -6836,23 +6835,29 @@ public class AudioService extends IAudioService.Stub
            requireModifyRouting |= true;
        }
        for (AudioMix mix : policyConfig.getMixes()) {
            // If mix is trying to capture USAGE_VOICE_COMMUNICATION using playback capture
            if (isVoiceCommunicationPlaybackCaptureMix(mix)) {
                // 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)) {
            // If mix is requesting privileged capture
            if (mix.getRule().allowPrivilegedPlaybackCapture()) {
                // then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission
                requireCaptureAudioOrMediaOutputPerm |= true;

                // and its format must be low quality enough
                String error = mix.canBeUsedForPrivilegedCapture(mix.getFormat());
                if (error != null) {
                    Log.e(TAG, error);
                    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
@@ -6872,14 +6877,20 @@ public class AudioService extends IAudioService.Stub
            return false;
        }

        if (requireVoiceComunicationOutputPerm
                && !callerHasPermission(
        if (voiceCommunicationCaptureMixes != null && voiceCommunicationCaptureMixes.size() > 0) {
            if (!callerHasPermission(
                    android.Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT)) {
                Log.e(TAG, "Privileged audio capture for voice communication requires "
                        + "CAPTURE_VOICE_COMMUNICATION_OUTPUT system permission");
                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)) {
            return false;
        }
@@ -6893,41 +6904,6 @@ public class AudioService extends IAudioService.Stub
        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) {
        return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
    }