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

Commit 597f0b5e authored by Eric Laurent's avatar Eric Laurent
Browse files

Add explicit private audio recording request

Add the possibility for apps to indicate that their
capture use case is private and that a privileged Assistant
should not be able to capture concurrently.
This allows to override the default behavior tied to the audio
source (e.g VOICE_COMMUNICATION is private by default but
UNPROCESSED is not).

- Add setPrivacySensitive() API to AudioRecord.Builder and
isPrivacySensitive() to AudioRecord
- Add setPrivacySensitive() and isPrivacySensitive() APIs to MediaRecorder.

Bug: 137850106
Test: CTS AudioRecordTest and MediaRecorderTest, manual audio smoke tests

Change-Id: I982b17cf08a9e5e0cf0501117bcaa6930b291f94
parent 34fc421d
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -23723,6 +23723,7 @@ package android.media {
    method public int getSampleRate();
    method public int getState();
    method public int getTimestamp(@NonNull android.media.AudioTimestamp, int);
    method public boolean isPrivacySensitive();
    method public int read(@NonNull byte[], int, int);
    method public int read(@NonNull byte[], int, int, int);
    method public int read(@NonNull short[], int, int);
@@ -23765,6 +23766,7 @@ package android.media {
    method @NonNull public android.media.AudioRecord.Builder setAudioPlaybackCaptureConfig(@NonNull android.media.AudioPlaybackCaptureConfiguration);
    method public android.media.AudioRecord.Builder setAudioSource(int) throws java.lang.IllegalArgumentException;
    method public android.media.AudioRecord.Builder setBufferSizeInBytes(int) throws java.lang.IllegalArgumentException;
    method @NonNull public android.media.AudioRecord.Builder setPrivacySensitive(boolean);
  }
  public static final class AudioRecord.MetricsConstants {
@@ -25778,6 +25780,7 @@ package android.media {
    method public android.media.AudioDeviceInfo getPreferredDevice();
    method public android.media.AudioDeviceInfo getRoutedDevice();
    method public android.view.Surface getSurface();
    method public boolean isPrivacySensitive();
    method public void pause() throws java.lang.IllegalStateException;
    method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
    method public void registerAudioRecordingCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioRecordingCallback);
@@ -25809,6 +25812,7 @@ package android.media {
    method public boolean setPreferredMicrophoneDirection(int);
    method public boolean setPreferredMicrophoneFieldDimension(@FloatRange(from=-1.0, to=1.0) float);
    method public void setPreviewDisplay(android.view.Surface);
    method public void setPrivacySensitive(boolean);
    method public void setProfile(android.media.CamcorderProfile);
    method public void setVideoEncoder(int) throws java.lang.IllegalStateException;
    method public void setVideoEncodingBitRate(int);
+16 −1
Original line number Diff line number Diff line
@@ -388,12 +388,21 @@ public final class AudioAttributes implements Parcelable {
     */
    public static final int FLAG_NO_SYSTEM_CAPTURE = 0x1 << 12;

    /**
     * @hide
     * Flag requesting private audio capture. When set in audio attributes passed to an
     * AudioRecord, this prevents a privileged Assistant from capturing audio while this
     * AudioRecord is active.
     */
    public static final int FLAG_CAPTURE_PRIVATE = 0x1 << 13;


    // Note that even though FLAG_MUTE_HAPTIC is stored as a flag bit, it is not here since
    // it is known as a boolean value outside of AudioAttributes.
    private static final int FLAG_ALL = FLAG_AUDIBILITY_ENFORCED | FLAG_SECURE | FLAG_SCO
            | FLAG_BEACON | FLAG_HW_AV_SYNC | FLAG_HW_HOTWORD | FLAG_BYPASS_INTERRUPTION_POLICY
            | FLAG_BYPASS_MUTE | FLAG_LOW_LATENCY | FLAG_DEEP_BUFFER | FLAG_NO_MEDIA_PROJECTION
            | FLAG_NO_SYSTEM_CAPTURE;
            | FLAG_NO_SYSTEM_CAPTURE | FLAG_CAPTURE_PRIVATE;
    private final static int FLAG_ALL_PUBLIC = FLAG_AUDIBILITY_ENFORCED |
            FLAG_HW_AV_SYNC | FLAG_LOW_LATENCY;

@@ -620,6 +629,12 @@ public final class AudioAttributes implements Parcelable {
            if (mMuteHapticChannels) {
                aa.mFlags |= FLAG_MUTE_HAPTIC;
            }
            // capturing for camcorder of communication is private by default to
            // reflect legacy behavior
            if (aa.mSource == MediaRecorder.AudioSource.VOICE_COMMUNICATION
                    || aa.mSource == MediaRecorder.AudioSource.CAMCORDER) {
                aa.mFlags |= FLAG_CAPTURE_PRIVATE;
            }
            aa.mTags = (HashSet<String>) mTags.clone();
            aa.mFormattedTags = TextUtils.join(";", mTags);
            if (mBundle != null) {
+74 −0
Original line number Diff line number Diff line
@@ -527,6 +527,11 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
        private AudioFormat mFormat;
        private int mBufferSizeInBytes;
        private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
        private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT;

        private static final int PRIVACY_SENSITIVE_DEFAULT = -1;
        private static final int PRIVACY_SENSITIVE_DISABLED = 0;
        private static final int PRIVACY_SENSITIVE_ENABLED = 1;

        /**
         * Constructs a new Builder with the default values as described above.
@@ -631,6 +636,36 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
            return this;
        }

        /**
         * Indicates that this capture request is privacy sensitive and that
         * any concurrent capture is not permitted.
         * <p>
         * The default is not privacy sensitive except when the audio source set with
         * {@link #setAudioSource(int)} is {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION} or
         * {@link MediaRecorder.AudioSource#CAMCORDER}.
         * <p>
         * Always takes precedence over default from audio source when set explicitly.
         * <p>
         * Using this API is only permitted when the audio source is one of:
         * <ul>
         * <li>{@link MediaRecorder.AudioSource#MIC}</li>
         * <li>{@link MediaRecorder.AudioSource#CAMCORDER}</li>
         * <li>{@link MediaRecorder.AudioSource#VOICE_RECOGNITION}</li>
         * <li>{@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}</li>
         * <li>{@link MediaRecorder.AudioSource#UNPROCESSED}</li>
         * <li>{@link MediaRecorder.AudioSource#VOICE_PERFORMANCE}</li>
         * </ul>
         * Invoking {@link #build()} will throw an UnsupportedOperationException if this
         * condition is not met.
         * @param privacySensitive True if capture from this AudioRecord must be marked as privacy
         * sensitive, false otherwise.
         */
        public @NonNull Builder setPrivacySensitive(boolean privacySensitive) {
            mPrivacySensitive =
                privacySensitive ? PRIVACY_SENSITIVE_ENABLED : PRIVACY_SENSITIVE_DISABLED;
            return this;
        }

        /**
         * @hide
         * To be only used by system components.
@@ -704,6 +739,34 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
                        .setInternalCapturePreset(MediaRecorder.AudioSource.DEFAULT)
                        .build();
            }

            // If mPrivacySensitive is default, the privacy flag is already set
            // according to audio source in audio attributes.
            if (mPrivacySensitive != PRIVACY_SENSITIVE_DEFAULT) {
                int source = mAttributes.getCapturePreset();
                if (source == MediaRecorder.AudioSource.REMOTE_SUBMIX
                        || source == MediaRecorder.AudioSource.RADIO_TUNER
                        || source == MediaRecorder.AudioSource.VOICE_DOWNLINK
                        || source == MediaRecorder.AudioSource.VOICE_UPLINK
                        || source == MediaRecorder.AudioSource.VOICE_CALL
                        || source == MediaRecorder.AudioSource.ECHO_REFERENCE) {
                    throw new UnsupportedOperationException(
                            "Cannot request private capture with source: " + source);
                }

                int flags = mAttributes.getAllFlags();
                if (mPrivacySensitive == PRIVACY_SENSITIVE_DISABLED) {
                    flags &= ~AudioAttributes.FLAG_CAPTURE_PRIVATE;
                } else if (mPrivacySensitive == PRIVACY_SENSITIVE_ENABLED) {
                    flags |= AudioAttributes.FLAG_CAPTURE_PRIVATE;
                }
                if (flags != mAttributes.getAllFlags()) {
                    mAttributes = new AudioAttributes.Builder(mAttributes)
                            .replaceFlags(flags)
                            .build();
                }
            }

            try {
                // If the buffer size is not specified,
                // use a single frame for the buffer size and let the
@@ -1062,6 +1125,17 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
        return mSessionId;
    }

    /**
     * Returns whether this AudioRecord is marked as privacy sensitive or not.
     * <p>
     * See {@link Builder#setPrivacySensitive(boolean)}
     * <p>
     * @return true if privacy sensitive, false otherwise
     */
    public boolean isPrivacySensitive() {
        return (mAudioAttributes.getAllFlags() & AudioAttributes.FLAG_CAPTURE_PRIVATE) != 0;
    }

    //---------------------------------------------------------
    // Transport control methods
    //--------------------
+40 −0
Original line number Diff line number Diff line
@@ -586,6 +586,46 @@ public class MediaRecorder implements AudioRouting,
        return AudioSource.VOICE_PERFORMANCE;
    }

    /**
     * Indicates that this capture request is privacy sensitive and that
     * any concurrent capture is not permitted.
     * <p>
     * The default is not privacy sensitive except when the audio source set with
     * {@link #setAudioSource(int)} is {@link AudioSource#VOICE_COMMUNICATION} or
     * {@link AudioSource#CAMCORDER}.
     * <p>
     * Always takes precedence over default from audio source when set explicitly.
     * <p>
     * Using this API is only permitted when the audio source is one of:
     * <ul>
     * <li>{@link AudioSource#MIC}</li>
     * <li>{@link AudioSource#CAMCORDER}</li>
     * <li>{@link AudioSource#VOICE_RECOGNITION}</li>
     * <li>{@link AudioSource#VOICE_COMMUNICATION}</li>
     * <li>{@link AudioSource#UNPROCESSED}</li>
     * <li>{@link AudioSource#VOICE_PERFORMANCE}</li>
     * </ul>
     * Invoking {@link #prepare()} will throw an IOException if this
     * condition is not met.
     * <p>
     * Must be called after {@link #setAudioSource(int)} and before {@link #setOutputFormat(int)}.
     * @param privacySensitive True if capture from this MediaRecorder must be marked as privacy
     * sensitive, false otherwise.
     * @throws IllegalStateException if called before {@link #setAudioSource(int)}
     * or after {@link #setOutputFormat(int)}
     */
    public native void setPrivacySensitive(boolean privacySensitive);

    /**
     * Returns whether this MediaRecorder is marked as privacy sensitive or not with
     * regard to audio capture.
     * <p>
     * See {@link #setPrivacySensitive(boolean)}
     * <p>
     * @return true if privacy sensitive, false otherwise
     */
    public native boolean isPrivacySensitive();

    /**
     * Sets the video source to be used for recording. If this method is not
     * called, the output file will not contain an video track. The source needs
+32 −0
Original line number Diff line number Diff line
@@ -226,6 +226,36 @@ android_media_MediaRecorder_setAudioSource(JNIEnv *env, jobject thiz, jint as)
    process_media_recorder_call(env, mr->setAudioSource(as), "java/lang/RuntimeException", "setAudioSource failed.");
}

static void
android_media_MediaRecorder_setPrivacySensitive(JNIEnv *env, jobject thiz, jboolean privacySensitive)
{
    ALOGV("%s(%s)", __func__, privacySensitive ? "true" : "false");

    sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
    if (mr == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return;
    }
    process_media_recorder_call(env, mr->setPrivacySensitive(privacySensitive),
        "java/lang/RuntimeException", "setPrivacySensitive failed.");
}

static jboolean
android_media_MediaRecorder_isPrivacySensitive(JNIEnv *env, jobject thiz)
{
    sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
    if (mr == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return false;
    }
    bool privacySensitive;
    process_media_recorder_call(env, mr->isPrivacySensitive(&privacySensitive),
        "java/lang/RuntimeException", "isPrivacySensitive failed.");

    ALOGV("%s() -> %s", __func__, privacySensitive ? "true" : "false");
    return privacySensitive;
}

static void
android_media_MediaRecorder_setOutputFormat(JNIEnv *env, jobject thiz, jint of)
{
@@ -817,6 +847,8 @@ static const JNINativeMethod gMethods[] = {
    {"setCamera",            "(Landroid/hardware/Camera;)V",    (void *)android_media_MediaRecorder_setCamera},
    {"setVideoSource",       "(I)V",                            (void *)android_media_MediaRecorder_setVideoSource},
    {"setAudioSource",       "(I)V",                            (void *)android_media_MediaRecorder_setAudioSource},
    {"setPrivacySensitive",  "(Z)V",                            (void *)android_media_MediaRecorder_setPrivacySensitive},
    {"isPrivacySensitive",  "()Z",                             (void *)android_media_MediaRecorder_isPrivacySensitive},
    {"setOutputFormat",      "(I)V",                            (void *)android_media_MediaRecorder_setOutputFormat},
    {"setVideoEncoder",      "(I)V",                            (void *)android_media_MediaRecorder_setVideoEncoder},
    {"setAudioEncoder",      "(I)V",                            (void *)android_media_MediaRecorder_setAudioEncoder},