diff options
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; } |