Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dc9a8e04 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add support for USAGE_VOICE_COMMUNICATION playback capture"

parents e8e3e651 f4e0836f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ package android {
    field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
    field public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT";
    field public static final String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT";
    field public static final String CAPTURE_VOICE_COMMUNICATION_OUTPUT = "android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT";
    field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
    field public static final String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST";
    field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
+18 −0
Original line number Diff line number Diff line
@@ -3793,6 +3793,24 @@
    <permission android:name="android.permission.CAPTURE_MEDIA_OUTPUT"
        android:protectionLevel="signature|privileged" />

    <!-- @SystemApi Allows an application to capture the audio played by other apps
        with the {@code USAGE_VOICE_COMMUNICATION} usage.

        The application may opt out of capturing by setting an allow capture policy of
        {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_NONE}.

        There are strong restriction listed at
        {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM}
        on what an app can do with the captured audio.

        See {@code CAPTURE_AUDIO_OUTPUT} and {@code CAPTURE_MEDIA_OUTPUT} for capturing
        audio use cases other than voice communication playback.

        <p>Not for use by third-party applications.</p>
        @hide -->
    <permission android:name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT"
        android:protectionLevel="signature|privileged" />

    <!-- @SystemApi Allows an application to capture audio for hotword detection.
         <p>Not for use by third-party applications.</p>
         @hide -->
+55 −2
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ 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;
@@ -89,6 +90,7 @@ 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;
@@ -6774,6 +6776,7 @@ public class AudioService extends IAudioService.Stub

        boolean requireValidProjection = false;
        boolean requireCaptureAudioOrMediaOutputPerm = false;
        boolean requireVoiceComunicationOutputPerm = false;
        boolean requireModifyRouting = false;

        if (hasFocusAccess || isVolumeController) {
@@ -6783,8 +6786,15 @@ public class AudioService extends IAudioService.Stub
            requireModifyRouting |= true;
        }
        for (AudioMix mix : policyConfig.getMixes()) {
            // If mix is requesting a privileged capture
            if (mix.getRule().allowPrivilegedPlaybackCapture()) {
            // 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)) {
                // then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission
                requireCaptureAudioOrMediaOutputPerm |= true;
                // and its format must be low quality enough
@@ -6812,6 +6822,14 @@ 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 (requireValidProjection && !canProjectAudio(projection)) {
            return false;
        }
@@ -6825,6 +6843,41 @@ 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;
    }