1 /* 2 * Copyright (C) 2022 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 com.android.audiopolicytest; 18 19 import static android.media.AudioAttributes.USAGE_MEDIA; 20 import static android.media.MediaRecorder.AudioSource.VOICE_RECOGNITION; 21 import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR; 22 import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS; 23 import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET; 24 import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE; 25 import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_AUDIO_SESSION_ID; 26 import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_UID; 27 import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET; 28 import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE; 29 import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID; 30 import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID; 31 32 import static org.hamcrest.MatcherAssert.assertThat; 33 import static org.hamcrest.collection.IsCollectionWithSize.hasSize; 34 import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; 35 import static org.junit.Assert.assertEquals; 36 import static org.junit.Assert.assertThrows; 37 38 39 import android.media.AudioAttributes; 40 import android.media.audiopolicy.AudioMixingRule; 41 import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion; 42 import android.platform.test.annotations.Presubmit; 43 44 import androidx.test.ext.junit.runners.AndroidJUnit4; 45 46 import org.hamcrest.CustomTypeSafeMatcher; 47 import org.hamcrest.Description; 48 import org.hamcrest.Matcher; 49 import org.junit.Test; 50 import org.junit.runner.RunWith; 51 52 /** 53 * Unit tests for AudioPolicy. 54 * 55 * Run with "atest AudioMixingRuleUnitTests". 56 */ 57 @Presubmit 58 @RunWith(AndroidJUnit4.class) 59 public class AudioMixingRuleUnitTests { 60 private static final AudioAttributes USAGE_MEDIA_AUDIO_ATTRIBUTES = 61 new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build(); 62 private static final AudioAttributes CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES = 63 new AudioAttributes.Builder().setCapturePreset(VOICE_RECOGNITION).build(); 64 private static final int TEST_UID = 42; 65 private static final int OTHER_UID = 77; 66 private static final int TEST_SESSION_ID = 1234; 67 68 @Test testConstructValidRule()69 public void testConstructValidRule() { 70 AudioMixingRule rule = new AudioMixingRule.Builder() 71 .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES) 72 .addMixRule(RULE_MATCH_UID, TEST_UID) 73 .excludeMixRule(RULE_MATCH_AUDIO_SESSION_ID, TEST_SESSION_ID) 74 .build(); 75 76 // Based on the rules, the mix type should fall back to MIX_ROLE_PLAYERS, 77 // since the rules are valid for both MIX_ROLE_PLAYERS & MIX_ROLE_INJECTOR. 78 assertEquals(rule.getTargetMixRole(), MIX_ROLE_PLAYERS); 79 assertThat(rule.getCriteria(), containsInAnyOrder( 80 isAudioMixMatchUsageCriterion(USAGE_MEDIA), 81 isAudioMixMatchUidCriterion(TEST_UID), 82 isAudioMixExcludeSessionCriterion(TEST_SESSION_ID))); 83 } 84 85 @Test testConstructRuleWithConflictingCriteriaFails()86 public void testConstructRuleWithConflictingCriteriaFails() { 87 assertThrows(IllegalArgumentException.class, 88 () -> new AudioMixingRule.Builder() 89 .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES) 90 .addMixRule(RULE_MATCH_UID, TEST_UID) 91 // Conflicts with previous criterion. 92 .addMixRule(RULE_EXCLUDE_UID, OTHER_UID) 93 .build()); 94 } 95 96 @Test testRuleBuilderDedupsCriteria()97 public void testRuleBuilderDedupsCriteria() { 98 AudioMixingRule rule = new AudioMixingRule.Builder() 99 .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES) 100 .addMixRule(RULE_MATCH_UID, TEST_UID) 101 // Identical to previous criterion. 102 .addMixRule(RULE_MATCH_UID, TEST_UID) 103 // Identical to first criterion. 104 .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES) 105 .build(); 106 107 assertThat(rule.getCriteria(), hasSize(2)); 108 assertThat(rule.getCriteria(), containsInAnyOrder( 109 isAudioMixMatchUsageCriterion(USAGE_MEDIA), 110 isAudioMixMatchUidCriterion(TEST_UID))); 111 } 112 113 @Test failsWhenAddAttributeRuleCalledWithInvalidType()114 public void failsWhenAddAttributeRuleCalledWithInvalidType() { 115 assertThrows(IllegalArgumentException.class, 116 () -> new AudioMixingRule.Builder() 117 // Rule match attribute usage requires AudioAttributes, not 118 // just the int enum value of the usage. 119 .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA) 120 .build()); 121 } 122 123 @Test failsWhenExcludeAttributeRuleCalledWithInvalidType()124 public void failsWhenExcludeAttributeRuleCalledWithInvalidType() { 125 assertThrows(IllegalArgumentException.class, 126 () -> new AudioMixingRule.Builder() 127 // Rule match attribute usage requires AudioAttributes, not 128 // just the int enum value of the usage. 129 .excludeMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA) 130 .build()); 131 } 132 133 @Test failsWhenAddIntRuleCalledWithInvalidType()134 public void failsWhenAddIntRuleCalledWithInvalidType() { 135 assertThrows(IllegalArgumentException.class, 136 () -> new AudioMixingRule.Builder() 137 // Rule match uid requires Integer not AudioAttributes. 138 .addMixRule(RULE_MATCH_UID, USAGE_MEDIA_AUDIO_ATTRIBUTES) 139 .build()); 140 } 141 142 @Test failsWhenExcludeIntRuleCalledWithInvalidType()143 public void failsWhenExcludeIntRuleCalledWithInvalidType() { 144 assertThrows(IllegalArgumentException.class, 145 () -> new AudioMixingRule.Builder() 146 // Rule match uid requires Integer not AudioAttributes. 147 .excludeMixRule(RULE_MATCH_UID, USAGE_MEDIA_AUDIO_ATTRIBUTES) 148 .build()); 149 } 150 151 @Test injectorMixTypeDeductionWithGenericRuleSucceeds()152 public void injectorMixTypeDeductionWithGenericRuleSucceeds() { 153 AudioMixingRule rule = new AudioMixingRule.Builder() 154 // UID rule can be used both with MIX_ROLE_PLAYERS and MIX_ROLE_INJECTOR. 155 .addMixRule(RULE_MATCH_UID, TEST_UID) 156 // Capture preset rule is only valid for injector, MIX_ROLE_INJECTOR should 157 // be deduced. 158 .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET, 159 CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES) 160 .build(); 161 162 assertEquals(rule.getTargetMixRole(), MIX_ROLE_INJECTOR); 163 assertThat(rule.getCriteria(), containsInAnyOrder( 164 isAudioMixMatchUidCriterion(TEST_UID), 165 isAudioMixMatchCapturePresetCriterion(VOICE_RECOGNITION))); 166 } 167 168 @Test settingTheMixTypeToIncompatibleInjectorMixFails()169 public void settingTheMixTypeToIncompatibleInjectorMixFails() { 170 assertThrows(IllegalArgumentException.class, 171 () -> new AudioMixingRule.Builder() 172 .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET, 173 CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES) 174 // Capture preset cannot be defined for MIX_ROLE_PLAYERS. 175 .setTargetMixRole(MIX_ROLE_PLAYERS) 176 .build()); 177 } 178 179 @Test addingPlayersOnlyRuleWithInjectorsOnlyRuleFails()180 public void addingPlayersOnlyRuleWithInjectorsOnlyRuleFails() { 181 assertThrows(IllegalArgumentException.class, 182 () -> new AudioMixingRule.Builder() 183 // MIX_ROLE_PLAYERS only rule. 184 .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES) 185 // MIX ROLE_INJECTOR only rule. 186 .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET, 187 CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES) 188 .build()); 189 } 190 191 @Test sessionIdRuleCompatibleWithPlayersMix()192 public void sessionIdRuleCompatibleWithPlayersMix() { 193 int sessionId = 42; 194 AudioMixingRule rule = new AudioMixingRule.Builder() 195 .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, sessionId) 196 .setTargetMixRole(MIX_ROLE_PLAYERS) 197 .build(); 198 199 assertEquals(rule.getTargetMixRole(), MIX_ROLE_PLAYERS); 200 assertThat(rule.getCriteria(), containsInAnyOrder(isAudioMixSessionCriterion(sessionId))); 201 } 202 203 @Test sessionIdRuleCompatibleWithInjectorMix()204 public void sessionIdRuleCompatibleWithInjectorMix() { 205 AudioMixingRule rule = new AudioMixingRule.Builder() 206 .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, TEST_SESSION_ID) 207 .setTargetMixRole(MIX_ROLE_INJECTOR) 208 .build(); 209 210 assertEquals(rule.getTargetMixRole(), MIX_ROLE_INJECTOR); 211 assertThat(rule.getCriteria(), 212 containsInAnyOrder(isAudioMixSessionCriterion(TEST_SESSION_ID))); 213 } 214 215 @Test audioMixingRuleWithNoRulesFails()216 public void audioMixingRuleWithNoRulesFails() { 217 assertThrows(IllegalArgumentException.class, 218 () -> new AudioMixingRule.Builder().build()); 219 } 220 221 isAudioMixUidCriterion(int uid, boolean exclude)222 private static Matcher isAudioMixUidCriterion(int uid, boolean exclude) { 223 return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("uid mix criterion") { 224 @Override 225 public boolean matchesSafely(AudioMixMatchCriterion item) { 226 int expectedRule = exclude ? RULE_EXCLUDE_UID : RULE_MATCH_UID; 227 return item.getRule() == expectedRule && item.getIntProp() == uid; 228 } 229 230 @Override 231 public void describeMismatchSafely( 232 AudioMixMatchCriterion item, Description mismatchDescription) { 233 mismatchDescription.appendText( 234 String.format("is not %s criterion with uid %d", 235 exclude ? "exclude" : "match", uid)); 236 } 237 }; 238 } 239 240 private static Matcher isAudioMixMatchUidCriterion(int uid) { 241 return isAudioMixUidCriterion(uid, /*exclude=*/ false); 242 } 243 244 private static Matcher isAudioMixCapturePresetCriterion(int audioSource, boolean exclude) { 245 return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("uid mix criterion") { 246 @Override 247 public boolean matchesSafely(AudioMixMatchCriterion item) { 248 int expectedRule = exclude 249 ? RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET 250 : RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET; 251 AudioAttributes attributes = item.getAudioAttributes(); 252 return item.getRule() == expectedRule 253 && attributes != null && attributes.getCapturePreset() == audioSource; 254 } 255 256 @Override 257 public void describeMismatchSafely( 258 AudioMixMatchCriterion item, Description mismatchDescription) { 259 mismatchDescription.appendText( 260 String.format("is not %s criterion with capture preset %d", 261 exclude ? "exclude" : "match", audioSource)); 262 } 263 }; 264 } 265 266 private static Matcher isAudioMixMatchCapturePresetCriterion(int audioSource) { 267 return isAudioMixCapturePresetCriterion(audioSource, /*exclude=*/ false); 268 } 269 270 private static Matcher isAudioMixUsageCriterion(int usage, boolean exclude) { 271 return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("usage mix criterion") { 272 @Override 273 public boolean matchesSafely(AudioMixMatchCriterion item) { 274 int expectedRule = 275 exclude ? RULE_EXCLUDE_ATTRIBUTE_USAGE : RULE_MATCH_ATTRIBUTE_USAGE; 276 AudioAttributes attributes = item.getAudioAttributes(); 277 return item.getRule() == expectedRule 278 && attributes != null && attributes.getUsage() == usage; 279 } 280 281 @Override 282 public void describeMismatchSafely( 283 AudioMixMatchCriterion item, Description mismatchDescription) { 284 mismatchDescription.appendText( 285 String.format("is not %s criterion with usage %d", 286 exclude ? "exclude" : "match", usage)); 287 } 288 }; 289 } 290 291 private static Matcher isAudioMixMatchUsageCriterion(int usage) { 292 return isAudioMixUsageCriterion(usage, /*exclude=*/ false); 293 } 294 295 private static Matcher isAudioMixSessionCriterion(int sessionId, boolean exclude) { 296 return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("sessionId mix criterion") { 297 @Override 298 public boolean matchesSafely(AudioMixMatchCriterion item) { 299 int excludeRule = 300 exclude ? RULE_EXCLUDE_AUDIO_SESSION_ID : RULE_MATCH_AUDIO_SESSION_ID; 301 return item.getRule() == excludeRule && item.getIntProp() == sessionId; 302 } 303 304 @Override 305 public void describeMismatchSafely( 306 AudioMixMatchCriterion item, Description mismatchDescription) { 307 mismatchDescription.appendText( 308 String.format("is not %s criterion with session id %d", 309 exclude ? "exclude" : "match", sessionId)); 310 } 311 }; 312 } 313 314 private static Matcher isAudioMixSessionCriterion(int sessionId) { 315 return isAudioMixSessionCriterion(sessionId, /*exclude=*/ false); 316 } 317 318 private static Matcher isAudioMixExcludeSessionCriterion(int sessionId) { 319 return isAudioMixSessionCriterion(sessionId, /*exclude=*/ true); 320 } 321 322 } 323