Removed recently added user id based routing for audio playback capture. The original goal of the user id based routing was for the routing to be controlled by audio policy and to controll where audio from a user could play. The audio playback capture was added to match similar API provided for audio playback capture based on application's uid. Currently, there is no appropiate use case for capturing based on user id. While this could prove useful in the future the appropiate use case research must be completed. Updated comments for audio policy user id based routing. Bug: 149419551 Test: atest AudioPlaybackCaptureTest Test: atest com.google.android.gts.audio.AudioHostTest#testUserIdDeviceAffinity Change-Id: I174cba618c4f66d11185e7ab5630094b4a8cb35b
251 lines
11 KiB
Java
251 lines
11 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.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
|
|
import android.media.projection.MediaProjection;
|
|
import android.os.RemoteException;
|
|
|
|
import com.android.internal.util.Preconditions;
|
|
|
|
import java.util.function.ToIntFunction;
|
|
|
|
/**
|
|
* Configuration for capturing audio played by other apps.
|
|
*
|
|
* When capturing audio signals played by other apps (and yours),
|
|
* you will only capture a mix of the audio signals played by players
|
|
* (such as AudioTrack or MediaPlayer) which present the following characteristics:
|
|
* <ul>
|
|
* <li> the usage value MUST be {@link AudioAttributes#USAGE_UNKNOWN} or
|
|
* {@link AudioAttributes#USAGE_GAME}
|
|
* or {@link AudioAttributes#USAGE_MEDIA}. All other usages CAN NOT be captured. </li>
|
|
* <li> AND the capture policy set by their app (with {@link AudioManager#setAllowedCapturePolicy})
|
|
* or on each player (with {@link AudioAttributes.Builder#setAllowedCapturePolicy}) is
|
|
* {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL}, whichever is the most strict. </li>
|
|
* <li> AND their app attribute allowAudioPlaybackCapture in their manifest
|
|
* MUST either be: <ul>
|
|
* <li> set to "true" </li>
|
|
* <li> not set, and their {@code targetSdkVersion} MUST be equal to or greater than
|
|
* {@link android.os.Build.VERSION_CODES#Q}.
|
|
* Ie. Apps that do not target at least Android Q must explicitly opt-in to be captured
|
|
* by a MediaProjection. </li></ul>
|
|
* <li> AND their apps MUST be in the same user profile as your app
|
|
* (eg work profile cannot capture user profile apps and vice-versa). </li>
|
|
* </ul>
|
|
*
|
|
* <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 Builder
|
|
* @see android.media.projection.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 Builder#Builder(MediaProjection)
|
|
*/
|
|
public @NonNull MediaProjection getMediaProjection() {
|
|
return mProjection;
|
|
}
|
|
|
|
/** @return the usages passed to {@link Builder#addMatchingUsage(int)}. */
|
|
@AttributeUsage
|
|
public @NonNull int[] getMatchingUsages() {
|
|
return getIntPredicates(AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE,
|
|
criterion -> criterion.getAudioAttributes().getUsage());
|
|
}
|
|
|
|
/** @return the UIDs passed to {@link Builder#addMatchingUid(int)}. */
|
|
public @NonNull int[] getMatchingUids() {
|
|
return getIntPredicates(AudioMixingRule.RULE_MATCH_UID,
|
|
criterion -> criterion.getIntProp());
|
|
}
|
|
|
|
/** @return the usages passed to {@link Builder#excludeUsage(int)}. */
|
|
@AttributeUsage
|
|
public @NonNull int[] getExcludeUsages() {
|
|
return getIntPredicates(AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE,
|
|
criterion -> criterion.getAudioAttributes().getUsage());
|
|
}
|
|
|
|
/** @return the UIDs passed to {@link Builder#excludeUid(int)}. */
|
|
public @NonNull int[] getExcludeUids() {
|
|
return getIntPredicates(AudioMixingRule.RULE_EXCLUDE_UID,
|
|
criterion -> criterion.getIntProp());
|
|
}
|
|
|
|
private int[] getIntPredicates(int rule,
|
|
ToIntFunction<AudioMixMatchCriterion> getPredicate) {
|
|
return mAudioMixingRule.getCriteria().stream()
|
|
.filter(criterion -> criterion.getRule() == rule)
|
|
.mapToInt(getPredicate)
|
|
.toArray();
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|
|
}
|