Merge "Fix voice communication audio playback capture"
This commit is contained in:
committed by
Android (Google) Code Review
commit
b773bf856d
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,12 +6877,18 @@ public class AudioService extends IAudioService.Stub
|
||||
return false;
|
||||
}
|
||||
|
||||
if (requireVoiceComunicationOutputPerm
|
||||
&& !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 (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)) {
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user