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: Kevin Rocard <krocard@google.com>
This commit is contained in:
Kevin Rocard
2019-04-03 12:37:32 -07:00
parent 61164db786
commit e5b29c98ee
4 changed files with 71 additions and 6 deletions

View File

@@ -400,7 +400,7 @@ public final class AudioAttributes implements Parcelable {
/**
* Indicates that the audio may be captured by any app.
*
* For privacy, the following usages can not be recorded: VOICE_COMMUNICATION*,
* For privacy, the following usages cannot be recorded: VOICE_COMMUNICATION*,
* USAGE_NOTIFICATION*, USAGE_ASSISTANCE* and USAGE_ASSISTANT.
*
* On {@link android.os.Build.VERSION_CODES#Q}, this means only {@link #USAGE_UNKNOWN},
@@ -413,11 +413,11 @@ public final class AudioAttributes implements Parcelable {
/**
* 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:
* - the audio can not leave the device
* - the audio can not be passed to a third party app
* - the audio can not be recorded at a higher quality then 16kHz 16bit mono
* - the audio cannot leave the device
* - the audio cannot be passed to a third party app
* - the audio cannot be recorded at a higher quality than 16kHz 16bit mono
*
* See {@link Builder#setAllowedCapturePolicy}.
*/

View File

@@ -91,6 +91,20 @@ public class AudioMix {
*/
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;
// 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;
/** 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.
* @return one of {@link #MIX_STATE_DISABLED}, {@link #MIX_STATE_IDLE},
@@ -140,7 +163,8 @@ public class AudioMix {
return mRouteFlags;
}
AudioFormat getFormat() {
/** @hide */
public AudioFormat getFormat() {
return mFormat;
}
@@ -182,6 +206,31 @@ public class AudioMix {
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 */
@Override
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,
mDeviceAddress);
}

View File

@@ -365,6 +365,10 @@ public class AudioMixingRule {
/**
* 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
* to ignore the opt-out.
*

View File

@@ -6761,6 +6761,12 @@ public class AudioService extends IAudioService.Stub
if (mix.getRule().allowPrivilegedPlaybackCapture()) {
// then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission
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