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

Commit e5b29c98 authored by Kevin Rocard's avatar Kevin Rocard
Browse files

System apps are not allowed to record better then 16kHz mono



This privacy requirement was documented but was not implemented.

Test: atest android.media.cts.AudioPlaybackCaptureTest#testCaptureMediaUsage
Bug: 129948989
Change-Id: I256e6ed1965263416a893393d5462ec73099751e
Signed-off-by: default avatarKevin Rocard <krocard@google.com>
parent 61164db7
Loading
Loading
Loading
Loading
+5 −5
Original line number Original line Diff line number Diff line
@@ -413,11 +413,11 @@ public final class AudioAttributes implements Parcelable {
    /**
    /**
     * Indicates that the audio may only be captured by system apps.
     * Indicates that the audio may only be captured by system apps.
     *
     *
     * System apps can capture for many purposes like accessibility, user guidance...
     * System apps can capture for many purposes like accessibility, live captions, user guidance...
     * but abide to the following restrictions:
     * but abide to the following restrictions:
     *  - the audio cannot leave the device
     *  - the audio cannot leave the device
     *  - the audio cannot be passed to a third party app
     *  - the audio cannot be passed to a third party app
     *  - the audio can not be recorded at a higher quality then 16kHz 16bit mono
     *  - the audio cannot be recorded at a higher quality than 16kHz 16bit mono
     *
     *
     * See {@link Builder#setAllowedCapturePolicy}.
     * See {@link Builder#setAllowedCapturePolicy}.
     */
     */
+56 −1
Original line number Original line Diff line number Diff line
@@ -91,6 +91,20 @@ public class AudioMix {
     */
     */
    public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;
    public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;


    /**
     * An audio mix behavior where the targeted audio is played unaffected but a copy is
     * accessible for capture through {@link AudioRecord}.
     *
     * Only capture of playback is supported, not capture of capture.
     * Use concurrent capture instead to capture what is captured by other apps.
     *
     * The captured audio is an approximation of the played audio.
     * Effects and volume are not applied, and track are mixed with different delay then in the HAL.
     * As a result, this API is not suitable for echo cancelling.
     * @hide
     */
    public static final int ROUTE_FLAG_LOOP_BACK_RENDER = ROUTE_FLAG_LOOP_BACK | ROUTE_FLAG_RENDER;

    private static final int ROUTE_FLAG_SUPPORTED = ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK;
    private static final int ROUTE_FLAG_SUPPORTED = ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK;


    // MIX_TYPE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h
    // MIX_TYPE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h
@@ -125,6 +139,15 @@ public class AudioMix {
     */
     */
    public static final int MIX_STATE_MIXING = 1;
    public static final int MIX_STATE_MIXING = 1;


    /** Maximum sampling rate for privileged playback capture*/
    private static final int PRIVILEDGED_CAPTURE_MAX_SAMPLE_RATE = 16000;

    /** Maximum channel number for privileged playback capture*/
    private static final int PRIVILEDGED_CAPTURE_MAX_CHANNEL_NUMBER = 1;

    /** Maximum channel number for privileged playback capture*/
    private static final int PRIVILEDGED_CAPTURE_MAX_BYTES_PER_SAMPLE = 2;

    /**
    /**
     * The current mixing state.
     * The current mixing state.
     * @return one of {@link #MIX_STATE_DISABLED}, {@link #MIX_STATE_IDLE},
     * @return one of {@link #MIX_STATE_DISABLED}, {@link #MIX_STATE_IDLE},
@@ -140,7 +163,8 @@ public class AudioMix {
        return mRouteFlags;
        return mRouteFlags;
    }
    }


    AudioFormat getFormat() {
    /** @hide */
    public AudioFormat getFormat() {
        return mFormat;
        return mFormat;
    }
    }


@@ -182,6 +206,31 @@ public class AudioMix {
        return true;
        return true;
    }
    }


    /** @return an error string if the format would not allow Privileged playbackCapture
     *          null otherwise
     * @hide */
    public static String canBeUsedForPrivilegedCapture(AudioFormat format) {
        int sampleRate = format.getSampleRate();
        if (sampleRate > PRIVILEDGED_CAPTURE_MAX_SAMPLE_RATE || sampleRate <= 0) {
            return "Privileged audio capture sample rate " + sampleRate
                   + " can not be over " + PRIVILEDGED_CAPTURE_MAX_SAMPLE_RATE + "kHz";
        }
        int channelCount = format.getChannelCount();
        if (channelCount > PRIVILEDGED_CAPTURE_MAX_CHANNEL_NUMBER || channelCount <= 0) {
            return "Privileged audio capture channel count " + channelCount + " can not be over "
                   + PRIVILEDGED_CAPTURE_MAX_CHANNEL_NUMBER;
        }
        int encoding = format.getEncoding();
        if (!format.isPublicEncoding(encoding) || !format.isEncodingLinearPcm(encoding)) {
            return "Privileged audio capture encoding " + encoding + "is not linear";
        }
        if (format.getBytesPerSample(encoding) > PRIVILEDGED_CAPTURE_MAX_BYTES_PER_SAMPLE) {
            return "Privileged audio capture encoding " + encoding + " can not be over "
                   + PRIVILEDGED_CAPTURE_MAX_BYTES_PER_SAMPLE + " bytes per sample";
        }
        return null;
    }

    /** @hide */
    /** @hide */
    @Override
    @Override
    public boolean equals(Object o) {
    public boolean equals(Object o) {
@@ -390,6 +439,12 @@ public class AudioMix {
                    }
                    }
                }
                }
            }
            }
            if (mRule.allowPrivilegedPlaybackCapture()) {
                String error = AudioMix.canBeUsedForPrivilegedCapture(mFormat);
                if (error != null) {
                    throw new IllegalArgumentException(error);
                }
            }
            return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType,
            return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType,
                    mDeviceAddress);
                    mDeviceAddress);
        }
        }
+4 −0
Original line number Original line Diff line number Diff line
@@ -365,6 +365,10 @@ public class AudioMixingRule {
        /**
        /**
         * Set if the audio of app that opted out of audio playback capture should be captured.
         * Set if the audio of app that opted out of audio playback capture should be captured.
         *
         *
         * Caller of this method with <code>true</code>, MUST abide to the restriction listed in
         * {@link ALLOW_CAPTURE_BY_SYSTEM}, including but not limited to the captured audio
         * can not leave the capturing app, and the quality is limited to 16k mono.
         *
         * The permission {@link CAPTURE_AUDIO_OUTPUT} or {@link CAPTURE_MEDIA_OUTPUT} is needed
         * The permission {@link CAPTURE_AUDIO_OUTPUT} or {@link CAPTURE_MEDIA_OUTPUT} is needed
         * to ignore the opt-out.
         * to ignore the opt-out.
         *
         *
+6 −0
Original line number Original line Diff line number Diff line
@@ -6761,6 +6761,12 @@ public class AudioService extends IAudioService.Stub
            if (mix.getRule().allowPrivilegedPlaybackCapture()) {
            if (mix.getRule().allowPrivilegedPlaybackCapture()) {
                // then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission
                // then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission
                requireCaptureAudioOrMediaOutputPerm |= true;
                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 RENDER|LOOPBACK, then an audio MediaProjection is enough
            // If mix is RENDER|LOOPBACK, then an audio MediaProjection is enough