summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/jni/android_media_AudioSystem.cpp6
-rw-r--r--media/java/android/media/audiopolicy/AudioMix.java10
-rw-r--r--media/java/android/media/audiopolicy/AudioMixingRule.java65
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicyConfig.java8
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java82
5 files changed, 113 insertions, 58 deletions
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index b4590f4f8b23..87be5f63c499 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -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);
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index dd9877a9c706..61113bc858db 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -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) {
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index bca3fa7834b4..68c9593d102d 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -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,6 +207,8 @@ public class AudioMixingRule {
public ArrayList<AudioMixMatchCriterion> getCriteria() { return mCriteria; }
@UnsupportedAppUsage
private boolean mAllowPrivilegedPlaybackCapture = false;
+ @UnsupportedAppUsage
+ private boolean mVoiceCommunicationCaptureAllowed = false;
/** @hide */
public boolean allowPrivilegedPlaybackCapture() {
@@ -195,6 +216,16 @@ public class AudioMixingRule {
}
/** @hide */
+ public boolean voiceCommunicationCaptureAllowed() {
+ return mVoiceCommunicationCaptureAllowed;
+ }
+
+ /** @hide */
+ public void setVoiceCommunicationCaptureAllowed(boolean allowed) {
+ mVoiceCommunicationCaptureAllowed = allowed;
+ }
+
+ /** @hide */
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -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.
@@ -401,6 +440,23 @@ public class AudioMixingRule {
}
/**
+ * 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.
* @param rule
@@ -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);
}
}
}
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index b048158c3979..91b9bb3f64c0 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -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) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 342ce22066b6..cf45f6cba4eb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -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;
@@ -90,7 +89,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;
@@ -6805,8 +6803,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;
@@ -6815,23 +6814,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
@@ -6851,12 +6856,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)) {
@@ -6872,41 +6883,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;
}