Loading core/jni/android_media_AudioSystem.cpp +6 −0 Original line number Diff line number Diff line Loading @@ -147,6 +147,7 @@ static jclass gAudioMixingRuleClass; static struct { jfieldID mCriteria; jfieldID mAllowPrivilegedPlaybackCapture; jfieldID mVoiceCommunicationCaptureAllowed; // other fields unused by JNI } gAudioMixingRuleFields; Loading Loading @@ -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); Loading Loading @@ -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); Loading media/java/android/media/audiopolicy/AudioMix.java +10 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading media/java/android/media/audiopolicy/AudioMixingRule.java +61 −4 Original line number Diff line number Diff line Loading @@ -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; } /** Loading Loading @@ -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; Loading @@ -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) { Loading @@ -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) { Loading Loading @@ -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. Loading Loading @@ -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. Loading Loading @@ -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); } } } media/java/android/media/audiopolicy/AudioPolicyConfig.java +7 −1 Original line number Diff line number Diff line Loading @@ -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()); Loading Loading @@ -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++) { Loading Loading @@ -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) { Loading services/core/java/com/android/server/audio/AudioService.java +29 −53 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -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 Loading @@ -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; } Loading @@ -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; } Loading Loading
core/jni/android_media_AudioSystem.cpp +6 −0 Original line number Diff line number Diff line Loading @@ -147,6 +147,7 @@ static jclass gAudioMixingRuleClass; static struct { jfieldID mCriteria; jfieldID mAllowPrivilegedPlaybackCapture; jfieldID mVoiceCommunicationCaptureAllowed; // other fields unused by JNI } gAudioMixingRuleFields; Loading Loading @@ -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); Loading Loading @@ -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); Loading
media/java/android/media/audiopolicy/AudioMix.java +10 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading
media/java/android/media/audiopolicy/AudioMixingRule.java +61 −4 Original line number Diff line number Diff line Loading @@ -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; } /** Loading Loading @@ -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; Loading @@ -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) { Loading @@ -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) { Loading Loading @@ -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. Loading Loading @@ -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. Loading Loading @@ -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); } } }
media/java/android/media/audiopolicy/AudioPolicyConfig.java +7 −1 Original line number Diff line number Diff line Loading @@ -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()); Loading Loading @@ -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++) { Loading Loading @@ -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) { Loading
services/core/java/com/android/server/audio/AudioService.java +29 −53 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -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 Loading @@ -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; } Loading @@ -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; } Loading