Merge "Add explicit private audio recording request"

This commit is contained in:
Eric Laurent
2019-12-06 19:21:53 +00:00
committed by Android (Google) Code Review
5 changed files with 166 additions and 1 deletions

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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
//--------------------

View File

@@ -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

View File

@@ -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},