Apps were previously forced to set their allowed capture policy from either their manifest which is not flexible or from each track which is a very fine grain but difficult when using libraries like exoplayer. Thus add an application level policy set with AudioManager. Test: atest android.media.cts.AudioPlaybackCaptureTest#testCaptureMediaUsage Bug: 111453086 Change-Id: Id360f1c3f3157c60694c039e51ff16e71d8f93cd Signed-off-by: Kevin Rocard <krocard@google.com>
206 lines
8.6 KiB
Java
206 lines
8.6 KiB
Java
/*
|
|
* Copyright (C) 2019 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.media;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.media.AudioAttributes.AttributeUsage;
|
|
import android.media.audiopolicy.AudioMix;
|
|
import android.media.audiopolicy.AudioMixingRule;
|
|
import android.media.projection.MediaProjection;
|
|
import android.os.RemoteException;
|
|
|
|
import com.android.internal.util.Preconditions;
|
|
|
|
/**
|
|
* Configuration for capturing audio played by other apps.
|
|
*
|
|
* Only the following audio can be captured:
|
|
* - usage MUST be {@link AudioAttributes#USAGE_UNKNOWN} or {@link AudioAttributes#USAGE_GAME}
|
|
* or {@link AudioAttributes#USAGE_MEDIA}. All other usages CAN NOT be captured.
|
|
* - audio attributes MUST have its ${@link AudioAttributes.Builder#setAllowedCapturePolicy}
|
|
* to {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL}.
|
|
* - played by apps that MUST be in the same user profile as the capturing app
|
|
* (eg work profile can not capture user profile apps and vice-versa).
|
|
* - played by apps for which the attribute allowAudioPlaybackCapture in their manifest
|
|
* MUST either be:
|
|
* * set to "true"
|
|
* * not set, and their targetSdkVersion MUST be equal or higher to
|
|
* {@link android.os.Build.VERSION_CODES#Q}.
|
|
*
|
|
* <p>An example for creating a capture configuration for capturing all media playback:
|
|
*
|
|
* <pre>
|
|
* MediaProjection mediaProjection;
|
|
* // Retrieve a audio capable projection from the MediaProjectionManager
|
|
* AudioPlaybackCaptureConfiguration config =
|
|
* new AudioPlaybackCaptureConfiguration.Builder(mediaProjection)
|
|
* .addMatchingUsage(AudioAttributes.USAGE_MEDIA)
|
|
* .build();
|
|
* AudioRecord record = new AudioRecord.Builder()
|
|
* .setAudioPlaybackCaptureConfig(config)
|
|
* .build();
|
|
* </pre>
|
|
*
|
|
* @see MediaProjectionManager#getMediaProjection(int, Intent)
|
|
* @see AudioRecord.Builder#setAudioPlaybackCaptureConfig(AudioPlaybackCaptureConfiguration)
|
|
*/
|
|
public final class AudioPlaybackCaptureConfiguration {
|
|
|
|
private final AudioMixingRule mAudioMixingRule;
|
|
private final MediaProjection mProjection;
|
|
|
|
private AudioPlaybackCaptureConfiguration(AudioMixingRule audioMixingRule,
|
|
MediaProjection projection) {
|
|
mAudioMixingRule = audioMixingRule;
|
|
mProjection = projection;
|
|
}
|
|
|
|
/**
|
|
* @return the {@code MediaProjection} used to build this object.
|
|
* @see {@code Builder.Builder}
|
|
*/
|
|
public @NonNull MediaProjection getMediaProjection() {
|
|
return mProjection;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a mix that routes audio back into the app while still playing it from the speakers.
|
|
*
|
|
* @param audioFormat The format in which to capture the audio.
|
|
*/
|
|
@NonNull AudioMix createAudioMix(@NonNull AudioFormat audioFormat) {
|
|
return new AudioMix.Builder(mAudioMixingRule)
|
|
.setFormat(audioFormat)
|
|
.setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER)
|
|
.build();
|
|
}
|
|
|
|
/** Builder for creating {@link AudioPlaybackCaptureConfiguration} instances. */
|
|
public static final class Builder {
|
|
|
|
private static final int MATCH_TYPE_UNSPECIFIED = 0;
|
|
private static final int MATCH_TYPE_INCLUSIVE = 1;
|
|
private static final int MATCH_TYPE_EXCLUSIVE = 2;
|
|
|
|
private static final String ERROR_MESSAGE_MISMATCHED_RULES =
|
|
"Inclusive and exclusive usage rules cannot be combined";
|
|
private static final String ERROR_MESSAGE_START_ACTIVITY_FAILED =
|
|
"startActivityForResult failed";
|
|
private static final String ERROR_MESSAGE_NON_AUDIO_PROJECTION =
|
|
"MediaProjection can not project audio";
|
|
|
|
private final AudioMixingRule.Builder mAudioMixingRuleBuilder;
|
|
private final MediaProjection mProjection;
|
|
private int mUsageMatchType = MATCH_TYPE_UNSPECIFIED;
|
|
private int mUidMatchType = MATCH_TYPE_UNSPECIFIED;
|
|
|
|
/** @param projection A MediaProjection that supports audio projection. */
|
|
public Builder(@NonNull MediaProjection projection) {
|
|
Preconditions.checkNotNull(projection);
|
|
try {
|
|
Preconditions.checkArgument(projection.getProjection().canProjectAudio(),
|
|
ERROR_MESSAGE_NON_AUDIO_PROJECTION);
|
|
} catch (RemoteException e) {
|
|
throw e.rethrowFromSystemServer();
|
|
}
|
|
mProjection = projection;
|
|
mAudioMixingRuleBuilder = new AudioMixingRule.Builder();
|
|
}
|
|
|
|
/**
|
|
* Only capture audio output with the given {@link AudioAttributes}.
|
|
*
|
|
* <p>If called multiple times, will capture audio output that matches any of the given
|
|
* attributes.
|
|
*
|
|
* @throws IllegalStateException if called in conjunction with
|
|
* {@link #excludeUsage(int)}.
|
|
*/
|
|
public @NonNull Builder addMatchingUsage(@AttributeUsage int usage) {
|
|
Preconditions.checkState(
|
|
mUsageMatchType != MATCH_TYPE_EXCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
|
|
mAudioMixingRuleBuilder.addRule(new AudioAttributes.Builder().setUsage(usage).build(),
|
|
AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
|
|
mUsageMatchType = MATCH_TYPE_INCLUSIVE;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Only capture audio output by app with the matching {@code uid}.
|
|
*
|
|
* <p>If called multiple times, will capture audio output by apps whose uid is any of the
|
|
* given uids.
|
|
*
|
|
* @throws IllegalStateException if called in conjunction with {@link #excludeUid(int)}.
|
|
*/
|
|
public @NonNull Builder addMatchingUid(int uid) {
|
|
Preconditions.checkState(
|
|
mUidMatchType != MATCH_TYPE_EXCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
|
|
mAudioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid);
|
|
mUidMatchType = MATCH_TYPE_INCLUSIVE;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Only capture audio output that does not match the given {@link AudioAttributes}.
|
|
*
|
|
* <p>If called multiple times, will capture audio output that does not match any of the
|
|
* given attributes.
|
|
*
|
|
* @throws IllegalStateException if called in conjunction with
|
|
* {@link #addMatchingUsage(int)}.
|
|
*/
|
|
public @NonNull Builder excludeUsage(@AttributeUsage int usage) {
|
|
Preconditions.checkState(
|
|
mUsageMatchType != MATCH_TYPE_INCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
|
|
mAudioMixingRuleBuilder.excludeRule(new AudioAttributes.Builder()
|
|
.setUsage(usage)
|
|
.build(),
|
|
AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
|
|
mUsageMatchType = MATCH_TYPE_EXCLUSIVE;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Only capture audio output by apps that do not have the matching {@code uid}.
|
|
*
|
|
* <p>If called multiple times, will capture audio output by apps whose uid is not any of
|
|
* the given uids.
|
|
*
|
|
* @throws IllegalStateException if called in conjunction with {@link #addMatchingUid(int)}.
|
|
*/
|
|
public @NonNull Builder excludeUid(int uid) {
|
|
Preconditions.checkState(
|
|
mUidMatchType != MATCH_TYPE_INCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
|
|
mAudioMixingRuleBuilder.excludeMixRule(AudioMixingRule.RULE_MATCH_UID, uid);
|
|
mUidMatchType = MATCH_TYPE_EXCLUSIVE;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Builds the configuration instance.
|
|
*
|
|
* @throws UnsupportedOperationException if the parameters set are incompatible.
|
|
*/
|
|
public @NonNull AudioPlaybackCaptureConfiguration build() {
|
|
return new AudioPlaybackCaptureConfiguration(mAudioMixingRuleBuilder.build(),
|
|
mProjection);
|
|
}
|
|
}
|
|
}
|