diff options
| -rw-r--r-- | api/current.txt | 4 | ||||
| -rw-r--r-- | media/java/android/media/AudioAttributes.java | 17 | ||||
| -rw-r--r-- | media/java/android/media/AudioRecord.java | 74 | ||||
| -rw-r--r-- | media/java/android/media/MediaRecorder.java | 40 | ||||
| -rw-r--r-- | media/jni/android_media_MediaRecorder.cpp | 32 |
5 files changed, 166 insertions, 1 deletions
diff --git a/api/current.txt b/api/current.txt index eb506e8d6b85..3cf158dbf142 100644 --- a/api/current.txt +++ b/api/current.txt @@ -23794,6 +23794,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); @@ -23836,6 +23837,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 { @@ -25899,6 +25901,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); @@ -25930,6 +25933,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); diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index c701d2aa580c..c03e8e20175d 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -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) { diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 0254c9721019..95afb090fcca 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -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. @@ -632,6 +637,36 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, } /** + * 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. * @param sessionId ID of audio session the AudioRecord must be attached to, or @@ -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 //-------------------- diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 9723652b5bd3..abb820645ae7 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -587,6 +587,46 @@ public class MediaRecorder implements AudioRouting, } /** + * 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 * to be specified before setting recording-parameters or encoders. Call diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 24fff0635238..f8ba36d99de7 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -227,6 +227,36 @@ android_media_MediaRecorder_setAudioSource(JNIEnv *env, jobject thiz, jint as) } 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) { ALOGV("setOutputFormat(%d)", 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}, |