summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt4
-rw-r--r--media/java/android/media/AudioAttributes.java17
-rw-r--r--media/java/android/media/AudioRecord.java74
-rw-r--r--media/java/android/media/MediaRecorder.java40
-rw-r--r--media/jni/android_media_MediaRecorder.cpp32
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},