1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media; 18 19 import android.annotation.NonNull; 20 import android.media.AudioAttributes.AttributeUsage; 21 import android.media.audiopolicy.AudioMix; 22 import android.media.audiopolicy.AudioMixingRule; 23 import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion; 24 import android.media.projection.MediaProjection; 25 import android.os.RemoteException; 26 27 import com.android.internal.util.Preconditions; 28 29 import java.util.function.ToIntFunction; 30 31 /** 32 * Configuration for capturing audio played by other apps. 33 * 34 * When capturing audio signals played by other apps (and yours), 35 * you will only capture a mix of the audio signals played by players 36 * (such as AudioTrack or MediaPlayer) which present the following characteristics: 37 * <ul> 38 * <li> the usage value MUST be {@link AudioAttributes#USAGE_UNKNOWN} or 39 * {@link AudioAttributes#USAGE_GAME} 40 * or {@link AudioAttributes#USAGE_MEDIA}. All other usages CAN NOT be captured. </li> 41 * <li> AND the capture policy set by their app (with {@link AudioManager#setAllowedCapturePolicy}) 42 * or on each player (with {@link AudioAttributes.Builder#setAllowedCapturePolicy}) is 43 * {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL}, whichever is the most strict. </li> 44 * <li> AND their app attribute allowAudioPlaybackCapture in their manifest 45 * MUST either be: <ul> 46 * <li> set to "true" </li> 47 * <li> not set, and their {@code targetSdkVersion} MUST be equal to or greater than 48 * {@link android.os.Build.VERSION_CODES#Q}. 49 * Ie. Apps that do not target at least Android Q must explicitly opt-in to be captured 50 * by a MediaProjection. </li></ul> 51 * <li> AND their apps MUST be in the same user profile as your app 52 * (eg work profile cannot capture user profile apps and vice-versa). </li> 53 * </ul> 54 * 55 * <p>An example for creating a capture configuration for capturing all media playback: 56 * 57 * <pre> 58 * MediaProjection mediaProjection; 59 * // Retrieve a audio capable projection from the MediaProjectionManager 60 * AudioPlaybackCaptureConfiguration config = 61 * new AudioPlaybackCaptureConfiguration.Builder(mediaProjection) 62 * .addMatchingUsage(AudioAttributes.USAGE_MEDIA) 63 * .build(); 64 * AudioRecord record = new AudioRecord.Builder() 65 * .setAudioPlaybackCaptureConfig(config) 66 * .build(); 67 * </pre> 68 * 69 * @see Builder 70 * @see android.media.projection.MediaProjectionManager#getMediaProjection(int, Intent) 71 * @see AudioRecord.Builder#setAudioPlaybackCaptureConfig(AudioPlaybackCaptureConfiguration) 72 */ 73 public final class AudioPlaybackCaptureConfiguration { 74 75 private final AudioMixingRule mAudioMixingRule; 76 private final MediaProjection mProjection; 77 AudioPlaybackCaptureConfiguration(AudioMixingRule audioMixingRule, MediaProjection projection)78 private AudioPlaybackCaptureConfiguration(AudioMixingRule audioMixingRule, 79 MediaProjection projection) { 80 mAudioMixingRule = audioMixingRule; 81 mProjection = projection; 82 } 83 84 /** 85 * @return the {@code MediaProjection} used to build this object. 86 * @see Builder#Builder(MediaProjection) 87 */ getMediaProjection()88 public @NonNull MediaProjection getMediaProjection() { 89 return mProjection; 90 } 91 92 /** @return the usages passed to {@link Builder#addMatchingUsage(int)}. */ 93 @AttributeUsage getMatchingUsages()94 public @NonNull int[] getMatchingUsages() { 95 return getIntPredicates(AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE, 96 criterion -> criterion.getAudioAttributes().getUsage()); 97 } 98 99 /** @return the UIDs passed to {@link Builder#addMatchingUid(int)}. */ getMatchingUids()100 public @NonNull int[] getMatchingUids() { 101 return getIntPredicates(AudioMixingRule.RULE_MATCH_UID, 102 criterion -> criterion.getIntProp()); 103 } 104 105 /** @return the usages passed to {@link Builder#excludeUsage(int)}. */ 106 @AttributeUsage getExcludeUsages()107 public @NonNull int[] getExcludeUsages() { 108 return getIntPredicates(AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE, 109 criterion -> criterion.getAudioAttributes().getUsage()); 110 } 111 112 /** @return the UIDs passed to {@link Builder#excludeUid(int)}. */ getExcludeUids()113 public @NonNull int[] getExcludeUids() { 114 return getIntPredicates(AudioMixingRule.RULE_EXCLUDE_UID, 115 criterion -> criterion.getIntProp()); 116 } 117 getIntPredicates(int rule, ToIntFunction<AudioMixMatchCriterion> getPredicate)118 private int[] getIntPredicates(int rule, 119 ToIntFunction<AudioMixMatchCriterion> getPredicate) { 120 return mAudioMixingRule.getCriteria().stream() 121 .filter(criterion -> criterion.getRule() == rule) 122 .mapToInt(getPredicate) 123 .toArray(); 124 } 125 126 /** 127 * Returns a mix that routes audio back into the app while still playing it from the speakers. 128 * 129 * @param audioFormat The format in which to capture the audio. 130 */ createAudioMix(@onNull AudioFormat audioFormat)131 @NonNull AudioMix createAudioMix(@NonNull AudioFormat audioFormat) { 132 return new AudioMix.Builder(mAudioMixingRule) 133 .setFormat(audioFormat) 134 .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER) 135 .build(); 136 } 137 138 /** Builder for creating {@link AudioPlaybackCaptureConfiguration} instances. */ 139 public static final class Builder { 140 141 private static final int MATCH_TYPE_UNSPECIFIED = 0; 142 private static final int MATCH_TYPE_INCLUSIVE = 1; 143 private static final int MATCH_TYPE_EXCLUSIVE = 2; 144 145 private static final String ERROR_MESSAGE_MISMATCHED_RULES = 146 "Inclusive and exclusive usage rules cannot be combined"; 147 private static final String ERROR_MESSAGE_START_ACTIVITY_FAILED = 148 "startActivityForResult failed"; 149 private static final String ERROR_MESSAGE_NON_AUDIO_PROJECTION = 150 "MediaProjection can not project audio"; 151 152 private final AudioMixingRule.Builder mAudioMixingRuleBuilder; 153 private final MediaProjection mProjection; 154 private int mUsageMatchType = MATCH_TYPE_UNSPECIFIED; 155 private int mUidMatchType = MATCH_TYPE_UNSPECIFIED; 156 157 /** @param projection A MediaProjection that supports audio projection. */ Builder(@onNull MediaProjection projection)158 public Builder(@NonNull MediaProjection projection) { 159 Preconditions.checkNotNull(projection); 160 try { 161 Preconditions.checkArgument(projection.getProjection().canProjectAudio(), 162 ERROR_MESSAGE_NON_AUDIO_PROJECTION); 163 } catch (RemoteException e) { 164 throw e.rethrowFromSystemServer(); 165 } 166 mProjection = projection; 167 mAudioMixingRuleBuilder = new AudioMixingRule.Builder(); 168 } 169 170 /** 171 * Only capture audio output with the given {@link AudioAttributes}. 172 * 173 * <p>If called multiple times, will capture audio output that matches any of the given 174 * attributes. 175 * 176 * @throws IllegalStateException if called in conjunction with 177 * {@link #excludeUsage(int)}. 178 */ addMatchingUsage(@ttributeUsage int usage)179 public @NonNull Builder addMatchingUsage(@AttributeUsage int usage) { 180 Preconditions.checkState( 181 mUsageMatchType != MATCH_TYPE_EXCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES); 182 mAudioMixingRuleBuilder.addRule(new AudioAttributes.Builder().setUsage(usage).build(), 183 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE); 184 mUsageMatchType = MATCH_TYPE_INCLUSIVE; 185 return this; 186 } 187 188 /** 189 * Only capture audio output by app with the matching {@code uid}. 190 * 191 * <p>If called multiple times, will capture audio output by apps whose uid is any of the 192 * given uids. 193 * 194 * @throws IllegalStateException if called in conjunction with {@link #excludeUid(int)}. 195 */ addMatchingUid(int uid)196 public @NonNull Builder addMatchingUid(int uid) { 197 Preconditions.checkState( 198 mUidMatchType != MATCH_TYPE_EXCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES); 199 mAudioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid); 200 mUidMatchType = MATCH_TYPE_INCLUSIVE; 201 return this; 202 } 203 204 /** 205 * Only capture audio output that does not match the given {@link AudioAttributes}. 206 * 207 * <p>If called multiple times, will capture audio output that does not match any of the 208 * given attributes. 209 * 210 * @throws IllegalStateException if called in conjunction with 211 * {@link #addMatchingUsage(int)}. 212 */ excludeUsage(@ttributeUsage int usage)213 public @NonNull Builder excludeUsage(@AttributeUsage int usage) { 214 Preconditions.checkState( 215 mUsageMatchType != MATCH_TYPE_INCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES); 216 mAudioMixingRuleBuilder.excludeRule(new AudioAttributes.Builder() 217 .setUsage(usage) 218 .build(), 219 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE); 220 mUsageMatchType = MATCH_TYPE_EXCLUSIVE; 221 return this; 222 } 223 224 /** 225 * Only capture audio output by apps that do not have the matching {@code uid}. 226 * 227 * <p>If called multiple times, will capture audio output by apps whose uid is not any of 228 * the given uids. 229 * 230 * @throws IllegalStateException if called in conjunction with {@link #addMatchingUid(int)}. 231 */ excludeUid(int uid)232 public @NonNull Builder excludeUid(int uid) { 233 Preconditions.checkState( 234 mUidMatchType != MATCH_TYPE_INCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES); 235 mAudioMixingRuleBuilder.excludeMixRule(AudioMixingRule.RULE_MATCH_UID, uid); 236 mUidMatchType = MATCH_TYPE_EXCLUSIVE; 237 return this; 238 } 239 240 /** 241 * Builds the configuration instance. 242 * 243 * @throws UnsupportedOperationException if the parameters set are incompatible. 244 */ build()245 public @NonNull AudioPlaybackCaptureConfiguration build() { 246 return new AudioPlaybackCaptureConfiguration(mAudioMixingRuleBuilder.build(), 247 mProjection); 248 } 249 } 250 } 251