Files
frameworks_base/media/java/android/media/AudioPlaybackCaptureConfiguration.java
Kevin Rocard 7919c2acc9 Add application wide capture policy
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>
2019-03-22 15:28:51 -07:00

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