1 /* 2 * Copyright (C) 2016 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.audio.cts; 18 19 import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED; 20 import static android.app.AppOpsManager.MODE_ALLOWED; 21 import static android.app.AppOpsManager.MODE_IGNORED; 22 import static android.app.AppOpsManager.OPSTR_PLAY_AUDIO; 23 import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_ALL; 24 import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE; 25 import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM; 26 import static android.media.AudioManager.ADJUST_MUTE; 27 import static android.media.AudioManager.ADJUST_UNMUTE; 28 import static android.media.AudioManager.STREAM_NOTIFICATION; 29 import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS; 30 import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME; 31 import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_VOLUME; 32 import static android.media.AudioPlaybackConfiguration.MUTED_BY_VOLUME_SHAPER; 33 import static android.media.AudioTrack.WRITE_NON_BLOCKING; 34 import static android.media.cts.AudioHelper.createSoundDataInShortByteBuffer; 35 import static android.media.cts.AudioHelper.hasAudioSilentProperty; 36 37 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 38 39 import static com.android.compatibility.common.util.AppOpsUtils.getOpMode; 40 import static com.android.compatibility.common.util.AppOpsUtils.setOpMode; 41 42 import android.Manifest; 43 import android.annotation.NonNull; 44 import android.annotation.Nullable; 45 import android.annotation.RawRes; 46 import android.content.Context; 47 import android.content.pm.PackageManager; 48 import android.content.res.AssetFileDescriptor; 49 import android.media.AudioAttributes; 50 import android.media.AudioAttributes.CapturePolicy; 51 import android.media.AudioFormat; 52 import android.media.AudioManager; 53 import android.media.AudioPlaybackConfiguration; 54 import android.media.AudioSystem; 55 import android.media.AudioTrack; 56 import android.media.MediaPlayer; 57 import android.media.SoundPool; 58 import android.media.VolumeShaper; 59 import android.media.audio.Flags; 60 import android.media.cts.TestUtils; 61 import android.os.Handler; 62 import android.os.HandlerThread; 63 import android.os.Parcel; 64 import android.os.Process; 65 import android.platform.test.annotations.RequiresFlagsEnabled; 66 import android.util.Log; 67 68 import com.android.compatibility.common.util.ApiTest; 69 import com.android.compatibility.common.util.CtsAndroidTestCase; 70 import com.android.compatibility.common.util.FrameworkSpecificTest; 71 import com.android.internal.annotations.GuardedBy; 72 73 import java.io.IOException; 74 import java.lang.reflect.Method; 75 import java.nio.ByteBuffer; 76 import java.util.ArrayList; 77 import java.util.HashSet; 78 import java.util.List; 79 import java.util.Set; 80 import java.util.concurrent.atomic.AtomicBoolean; 81 import java.util.function.Consumer; 82 import java.util.function.Function; 83 import java.util.stream.Collectors; 84 85 @FrameworkSpecificTest 86 public class AudioPlaybackConfigurationTest extends CtsAndroidTestCase { 87 private static final String TAG = "AudioPlaybackConfigurationTest"; 88 89 private static final int TEST_TIMING_TOLERANCE_MS = 150; 90 /** acceptable timeout for the time it takes for a prepared MediaPlayer to have an audio device 91 * selected and reported when starting to play */ 92 private static final int PLAY_ROUTING_TIMING_TOLERANCE_MS = 500; 93 private static final int TEST_TIMEOUT_SOUNDPOOL_LOAD_MS = 3000; 94 private static final long MEDIAPLAYER_PREPARE_TIMEOUT_MS = 2000; 95 96 private static final int TEST_AUDIO_TRACK_SAMPLERATE = 48000; 97 private static final double TEST_AUDIO_TRACK_FREQUENCY = 440.0; 98 private static final int TEST_AUDIO_TRACK_CHANNELS = 2; 99 private static final int TEST_AUDIO_TRACK_PLAY_SECONDS = 2; 100 private static final double TEST_AUDIO_TRACK_SWEEP = 0; 101 102 // volume shaper duration in milliseconds. 103 private static final long VOLUME_SHAPER_DURATION_MS = 10; 104 105 private static final VolumeShaper.Configuration SHAPER_MUTE = 106 new VolumeShaper.Configuration.Builder() 107 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR) 108 .setCurve(new float[] { 0.f, 1.f } /* times */, 109 new float[] { 1.f, 0.f } /* volumes */) 110 .setDuration(VOLUME_SHAPER_DURATION_MS) 111 .build(); 112 113 /** 114 * Duplicating from {@link AudioPlaybackConfiguration} to make sure tests run properly 115 * without the newest SDK. 116 **/ 117 private static final int MUTED_BY_PORT_VOLUME = (1 << 6); 118 119 private VolumeShaper mMuteShaper; 120 121 // not declared inside test so it can be released in case of failure 122 private MediaPlayer mMp; 123 private SoundPool mSp; 124 private AudioTrack mAt; 125 126 private static final int TEST_USAGE = AudioAttributes.USAGE_NOTIFICATION; 127 private static final int TEST_CONTENT = AudioAttributes.CONTENT_TYPE_MOVIE; 128 private static final int TEST_STREAM_FOR_USAGE = STREAM_NOTIFICATION; 129 // A combination of less common flags are used intentionally to clearly identify attributes 130 private static final int TEST_ATTRIBUTE_FLAGS = AudioAttributes.FLAG_MUTE_HAPTIC; 131 private static final AudioAttributes TEST_AUDIO_ATTRIBUTES = 132 new AudioAttributes.Builder() 133 .setUsage(TEST_USAGE) 134 .setContentType(TEST_CONTENT) 135 .setFlags(TEST_ATTRIBUTE_FLAGS) 136 .build(); 137 private boolean mIsPrivileged = false; // if the player was setup with privileged permission 138 private final int mUid = Process.myUid(); 139 private final int mPid = Process.myPid(); 140 141 private final SafeWaitObject mMediaPlayerLock = new SafeWaitObject(); 142 143 @Override tearDown()144 protected void tearDown() throws Exception { 145 dropShellPermissionIdentity(); 146 // try/catch for every method in case the tests left the objects in various states 147 if (mMp != null) { 148 try { 149 mMp.stop(); 150 } catch (Exception e) { 151 Log.e(TAG, "Exception in MediaPlayer stop: " + e); 152 } 153 mMp.release(); 154 mMp = null; 155 } 156 if (mSp != null) { 157 mSp.release(); 158 mSp = null; 159 } 160 if (mAt != null) { 161 try { 162 mAt.stop(); 163 } catch (Exception e) { 164 Log.e(TAG, "Exception in AudioTrack stop: " + e); 165 } 166 mAt.release(); 167 mAt = null; 168 } 169 super.tearDown(); 170 } 171 172 // test writing to/ reading from a Parcel for an AudioPlaybackConfiguration instance. 173 // Since we can't create an AudioPlaybackConfiguration directly, we first need to 174 // play something to get one. 175 // FIXME: b/402529329 create and use AudioPlaybackConfiguration test API to test serialization testParcelableWriteToParcel()176 public void testParcelableWriteToParcel() throws Exception { 177 if (!isValidPlatform("testParcelableWriteToParcel")) return; 178 if (hasAudioSilentProperty()) { 179 // No reasons to test since the started MediaPlayer will be muted and inactive 180 Log.w(TAG, "Device has ro.audio.silent set, skipping testParcelableWriteToParcel"); 181 return; 182 } 183 184 // create a player, make it play so we can get an AudioPlaybackConfiguration instance 185 AudioManager am = new AudioManager(getContext()); 186 assertNotNull("Could not create AudioManager", am); 187 if (isRingerModeSilent(am)) { 188 Log.w(TAG, "skipping testParcelableWriteToParcel for RINGER_MODE_SILENT"); 189 return; 190 } 191 192 final AudioAttributes aa = (new AudioAttributes.Builder()) 193 .setUsage(TEST_USAGE) 194 .setContentType(TEST_CONTENT) 195 .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_NONE) 196 .build(); 197 List<AudioPlaybackConfiguration> configs; 198 final int session = am.generateAudioSessionId(); 199 mMp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, aa, session); 200 startMediaPlayerWithCheck(am, mMp, aa, session, null); 201 configs = am.getActivePlaybackConfigurations(); 202 assertTrue("No playback reported", configs.size() > 0); 203 stopMediaPlayerWithCheck(am, mMp, aa, session, null); 204 205 AudioPlaybackConfiguration configToParcel = null; 206 for (AudioPlaybackConfiguration config : configs) { 207 if (config.getAudioAttributes().equals(aa)) { 208 configToParcel = config; 209 break; 210 } 211 } 212 213 assertNotNull("Configuration not found during playback", configToParcel); 214 assertEquals(0, configToParcel.describeContents()); 215 216 final Parcel srcParcel = Parcel.obtain(); 217 final Parcel dstParcel = Parcel.obtain(); 218 219 configToParcel.writeToParcel(srcParcel, 0 /*no public flags for parcelling operations*/); 220 dstParcel.appendFrom(srcParcel, 0 /*offset*/, srcParcel.dataSize() /*size*/); 221 dstParcel.setDataPosition(0); 222 final AudioPlaybackConfiguration restoredConfig = 223 AudioPlaybackConfiguration.CREATOR.createFromParcel(dstParcel); 224 225 assertEquals("Marshalled/restored AudioAttributes don't match", 226 configToParcel.getAudioAttributes(), restoredConfig.getAudioAttributes()); 227 } 228 testGetterMediaPlayer()229 public void testGetterMediaPlayer() throws Exception { 230 if (!isValidPlatform("testGetterMediaPlayer")) return; 231 if (hasAudioSilentProperty()) { 232 // No reasons to test since the started MediaPlayer will be muted and inactive 233 Log.w(TAG, "Device has ro.audio.silent set, skipping testGetterMediaPlayer"); 234 return; 235 } 236 237 AudioManager am = new AudioManager(getContext()); 238 assertNotNull("Could not create AudioManager", am); 239 if (isRingerModeSilent(am)) { 240 Log.w(TAG, "skipping testGetterMediaPlayer for RINGER_MODE_SILENT"); 241 return; 242 } 243 244 final AudioAttributes aa = (new AudioAttributes.Builder()) 245 .setUsage(TEST_USAGE) 246 .setContentType(TEST_CONTENT) 247 .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_ALL) 248 .build(); 249 250 final Set<AudioPlaybackConfiguration> oldConfigs = 251 new HashSet<AudioPlaybackConfiguration>(am.getActivePlaybackConfigurations()); 252 253 final int session = am.generateAudioSessionId(); 254 255 mMp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, aa, session); 256 List<AudioPlaybackConfiguration> newConfigs = am.getActivePlaybackConfigurations(); 257 assertEquals( 258 "inactive MediaPlayer, number of configs shouldn't have changed", 259 0, 260 getAddedPlayerConfigs(oldConfigs, newConfigs, session, aa).size()); 261 startMediaPlayerWithCheck(am, mMp, aa, session, null /* callback */); 262 newConfigs = am.getActivePlaybackConfigurations(); 263 assertEquals("Expect at least one config after start", 1, newConfigs.size()); 264 265 stopMediaPlayerWithCheck(am, mMp, aa, session, null /* callback */); 266 267 // verify "privileged" fields aren't available through reflection 268 final AudioPlaybackConfiguration config = newConfigs.get(0); 269 final Class<?> confClass = config.getClass(); 270 final Method getClientUidMethod = confClass.getDeclaredMethod("getClientUid"); 271 final Method getClientPidMethod = confClass.getDeclaredMethod("getClientPid"); 272 final Method getPlayerTypeMethod = confClass.getDeclaredMethod("getPlayerType"); 273 final Method getSessionIdMethod = confClass.getDeclaredMethod("getSessionId"); 274 try { 275 Integer uid = (Integer) getClientUidMethod.invoke(config, (Object[]) null); 276 assertEquals("uid isn't protected", -1 /*expected*/, uid.intValue()); 277 Integer pid = (Integer) getClientPidMethod.invoke(config, (Object[]) null); 278 assertEquals("pid isn't protected", -1 /*expected*/, pid.intValue()); 279 Integer type = (Integer) getPlayerTypeMethod.invoke(config, (Object[]) null); 280 assertEquals("player type isn't protected", -1 /*expected*/, type.intValue()); 281 Integer sessionId = (Integer) getSessionIdMethod.invoke(config, (Object[]) null); 282 assertEquals("session ID isn't protected", 0 /*expected*/, sessionId.intValue()); 283 } catch (Exception e) { 284 fail("Exception thrown during reflection on config privileged fields"+ e); 285 } 286 assertEquals("spatialized field isn't protected", false, config.isSpatialized()); 287 assertEquals("sample rate field isn't protected", 0, config.getSampleRate()); 288 assertEquals("channel mask field isn't protected", 0, config.getChannelMask()); 289 } 290 testCallbackMediaPlayer()291 public void testCallbackMediaPlayer() throws Exception { 292 if (!isValidPlatform("testCallbackMediaPlayer")) return; 293 294 doTestCallbackMediaPlayer(false /* no custom Handler for callback */); 295 } 296 testCallbackMediaPlayerHandler()297 public void testCallbackMediaPlayerHandler() throws Exception { 298 if (!isValidPlatform("testCallbackMediaPlayerHandler")) return; 299 doTestCallbackMediaPlayer(true /* use custom Handler for callback */); 300 } 301 doTestCallbackMediaPlayer(boolean useHandlerInCallback)302 private void doTestCallbackMediaPlayer(boolean useHandlerInCallback) throws Exception { 303 final Handler h; 304 if (useHandlerInCallback) { 305 HandlerThread handlerThread = new HandlerThread(TAG); 306 handlerThread.start(); 307 h = new Handler(handlerThread.getLooper()); 308 } else { 309 h = null; 310 } 311 312 AudioManager am = new AudioManager(getContext()); 313 assertNotNull("Could not create AudioManager", am); 314 if (isRingerModeSilent(am)) { 315 Log.w(TAG, "skipping doTestCallbackMediaPlayer for RINGER_MODE_SILENT"); 316 return; 317 } 318 319 final int sessionId = am.generateAudioSessionId(); 320 final AudioAttributes aa = TEST_AUDIO_ATTRIBUTES; 321 322 final MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback(sessionId, aa); 323 MyAudioPlaybackCallback registeredCallback = null; 324 325 try { 326 mMp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, aa, sessionId); 327 am.registerAudioPlaybackCallback(callback, h /*handler*/); 328 registeredCallback = callback; 329 330 startMediaPlayerWithCheck(am, mMp, aa, sessionId, callback); 331 332 pauseMediaPlayerWithCheck(am, mMp, aa, sessionId, callback); 333 334 // unregister callback and start playback again 335 am.unregisterAudioPlaybackCallback(callback); 336 registeredCallback = null; 337 338 startMediaPlayerWithCheck(am, mMp, aa, sessionId, null /* callback */); 339 stopMediaPlayerWithCheck(am, mMp, aa, sessionId, null /* callback */); 340 } finally { 341 if (registeredCallback != null) { 342 am.unregisterAudioPlaybackCallback(registeredCallback); 343 } 344 if (h != null) { 345 h.getLooper().quit(); 346 } 347 } 348 } 349 testCallbackMediaPlayerRelease()350 public void testCallbackMediaPlayerRelease() throws Exception { 351 352 final HandlerThread handlerThread = new HandlerThread(TAG); 353 handlerThread.start(); 354 final Handler h = new Handler(handlerThread.getLooper()); 355 356 AudioManager am = new AudioManager(getContext()); 357 assertNotNull("Could not create AudioManager", am); 358 if (isRingerModeSilent(am)) { 359 Log.w(TAG, "skipping testCallbackMediaPlayerRelease for RINGER_MODE_SILENT"); 360 return; 361 } 362 363 final int sessionId = am.generateAudioSessionId(); 364 final AudioAttributes aa = TEST_AUDIO_ATTRIBUTES; 365 final MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback(sessionId, aa); 366 MyAudioPlaybackCallback registeredCallback = null; 367 368 try { 369 mMp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, aa, sessionId); 370 am.registerAudioPlaybackCallback(callback, h /*handler*/); 371 registeredCallback = callback; 372 373 startMediaPlayerWithCheck(am, mMp, aa, sessionId, callback); 374 375 // release the player without stopping or pausing it first 376 callback.reset(); 377 mMp.release(); 378 379 assertTrue("onPlaybackConfigChanged should have been called for PLAYER_STATE_RELEASED", 380 callback.waitForCallbacks(1, TEST_TIMING_TOLERANCE_MS)); 381 assertEquals( 382 "Should not have any matched active players after release", 383 0 /*expected*/, 384 callback.getMediaPlayerConfigs().size()); 385 } finally { 386 if (registeredCallback != null) { 387 am.unregisterAudioPlaybackCallback(registeredCallback); 388 } 389 if (h != null) { 390 h.getLooper().quit(); 391 } 392 } 393 } 394 testGetterSoundPool()395 public void testGetterSoundPool() throws Exception { 396 if (!isValidPlatform("testSoundPool")) return; 397 AudioManager am = new AudioManager(getContext()); 398 assertNotNull("Could not create AudioManager", am); 399 400 final int sessionId = am.generateAudioSessionId(); 401 final AudioAttributes spAttributes = 402 (new AudioAttributes.Builder()) 403 .setUsage(AudioAttributes.USAGE_NOTIFICATION) 404 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 405 .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_SYSTEM) 406 .build(); 407 final MyAudioPlaybackCallback callback = 408 new MyAudioPlaybackCallback(sessionId, spAttributes); 409 MyAudioPlaybackCallback registeredCallback = null; 410 int streamId = 0; // SoundPool.play() return non-zero streamID if successful 411 try { 412 am.registerAudioPlaybackCallback(callback, null /*handler*/); 413 registeredCallback = callback; 414 415 mSp = createSoundPool(sessionId, spAttributes); 416 streamId = playSoundPool(mSp, getContext()); 417 418 Thread.sleep(TEST_TIMING_TOLERANCE_MS); 419 420 mSp.autoPause(); 421 Thread.sleep(TEST_TIMING_TOLERANCE_MS); 422 423 // query how many active players after pausing 424 final List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations(); 425 int nbActiveSpPlayersAfterPause = 0; 426 for (AudioPlaybackConfiguration apc : configs) { 427 if (apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED 428 && apc.getSessionId() == sessionId) { 429 nbActiveSpPlayersAfterPause++; 430 } 431 } 432 assertEquals( 433 "Should not have any active SoundPool player after pausing", 434 0, 435 nbActiveSpPlayersAfterPause); 436 } finally { 437 stopSoundPool(mSp, streamId); 438 if (registeredCallback != null) { 439 am.unregisterAudioPlaybackCallback(callback); 440 } 441 } 442 } 443 444 @ApiTest(apis = {"android.media.AudioManager#getActivePlaybackConfigurations", 445 "android.media.AudioManager.AudioPlaybackCallback#onPlaybackConfigChanged"}) testGetterAndCallbackConsistency()446 public void testGetterAndCallbackConsistency() throws Exception { 447 if (!isValidPlatform("testGetterAndCallbackConsistency")) return; 448 449 AudioManager am = new AudioManager(getContext()); 450 assertNotNull("Could not create AudioManager", am); 451 if (isRingerModeSilent(am)) { 452 Log.w(TAG, "skipping testGetterAndCallbackConsistency for RINGER_MODE_SILENT"); 453 return; 454 } 455 456 final int soundPoolSessionId = am.generateAudioSessionId(); 457 final int mediaSessionId = am.generateAudioSessionId(); 458 final AudioAttributes aa = (new AudioAttributes.Builder()) 459 .setUsage(TEST_USAGE) 460 .setContentType(TEST_CONTENT) 461 .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_SYSTEM) 462 .build(); 463 final MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback(mediaSessionId, aa); 464 am.registerAudioPlaybackCallback(callback, null /*handler*/); 465 466 final AudioAttributes spAttributes = 467 (new AudioAttributes.Builder()) 468 .setUsage(AudioAttributes.USAGE_NOTIFICATION) 469 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 470 .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_SYSTEM) 471 .build(); 472 mSp = createSoundPool(soundPoolSessionId, spAttributes); 473 474 mMp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, aa, mediaSessionId); 475 int streamId = 0; // SoundPool.play() return non-zero streamID if successful 476 try { 477 streamId = playSoundPool(mSp, getContext()); 478 callback.reset(); 479 startMediaPlayerWithCheck(am, mMp, aa, mediaSessionId, callback); 480 481 mSp.autoPause(); 482 stopMediaPlayerWithCheck(am, mMp, aa, mediaSessionId, callback); 483 } finally { 484 stopSoundPool(mSp, streamId); 485 am.unregisterAudioPlaybackCallback(callback); 486 } 487 } 488 testGetAudioDeviceInfoMediaPlayerStart(boolean enableRoutedDeviceIdsFlag)489 private void testGetAudioDeviceInfoMediaPlayerStart(boolean enableRoutedDeviceIdsFlag) 490 throws Exception { 491 if (!isValidPlatform("testGetAudioDeviceInfoMediaPlayerStart")) return; 492 493 final HandlerThread handlerThread = new HandlerThread(TAG); 494 handlerThread.start(); 495 final Handler h = new Handler(handlerThread.getLooper()); 496 497 AudioManager am = new AudioManager(getContext()); 498 assertNotNull("Could not create AudioManager", am); 499 if (isRingerModeSilent(am)) { 500 Log.w(TAG, "skipping testGetAudioDeviceInfoMediaPlayerStart for RINGER_MODE_SILENT"); 501 return; 502 } 503 504 final int sessionId = am.generateAudioSessionId(); 505 final AudioAttributes aa = TEST_AUDIO_ATTRIBUTES; 506 final MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback(sessionId, aa); 507 MyAudioPlaybackCallback registeredCallback = null; 508 509 try { 510 adoptShellPermissionIdentity(Manifest.permission.MODIFY_AUDIO_ROUTING); 511 mMp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, aa, sessionId); 512 am.registerAudioPlaybackCallback(callback, h /*handler*/); 513 registeredCallback = callback; 514 515 startMediaPlayerWithCheck(am, mMp, aa, sessionId, callback); 516 synchronized (mMediaPlayerLock) { 517 // wait for the new configuration to propagate 518 assertTrue( 519 "No matching AudioAttributes from CB in time", 520 mMediaPlayerLock.waitFor( 521 TEST_TIMING_TOLERANCE_MS + PLAY_ROUTING_TIMING_TOLERANCE_MS, 522 () -> 523 hasDevice( 524 callback.getMediaPlayerConfigs(), 525 aa, 526 enableRoutedDeviceIdsFlag))); 527 } 528 stopMediaPlayerWithCheck(am, mMp, aa, sessionId, callback); 529 } finally { 530 if (registeredCallback != null) { 531 am.unregisterAudioPlaybackCallback(registeredCallback); 532 } 533 dropShellPermissionIdentity(); 534 if (h != null) { 535 h.getLooper().quit(); 536 } 537 } 538 } 539 testGetAudioDeviceInfoMediaPlayerStart()540 public void testGetAudioDeviceInfoMediaPlayerStart() throws Exception { 541 testGetAudioDeviceInfoMediaPlayerStart(false /*enableRoutedDeviceIdsFlag*/); 542 } 543 544 @RequiresFlagsEnabled(Flags.FLAG_ROUTED_DEVICE_IDS) testGetAudioDeviceInfosMediaPlayerStart()545 public void testGetAudioDeviceInfosMediaPlayerStart() throws Exception { 546 testGetAudioDeviceInfoMediaPlayerStart(true /*enableRoutedDeviceIdsFlag*/); 547 } 548 549 @ApiTest(apis = {"android.media.AudioManager#getActivePlaybackConfigurations", 550 "android.media.AudioManager.AudioPlaybackCallback#onPlaybackConfigChanged", 551 "android.media.AudioManager.AudioPlaybackCallback#isMuted", 552 "android.media.AudioManager.AudioPlaybackCallback#getMutedBy"}) testAudioTrackMuteFromAppOpsNotification()553 public void testAudioTrackMuteFromAppOpsNotification() throws Exception { 554 if (isWatch()) { 555 Log.w(TAG, "Skip testAudioTrackMuteFromAppOpsNotification for Wear"); 556 return; 557 } 558 if (!isValidPlatform("testAudioTrackMuteFromAppOpsNotification")) return; 559 if (hasAudioSilentProperty()) { 560 Log.w(TAG, "Device has ro.audio.silent set, skipping " 561 + "testAudioTrackMuteFromAppOpsNotification"); 562 return; 563 } 564 565 final AudioAttributes aa = TEST_AUDIO_ATTRIBUTES; 566 initializeAudioTrack(aa); 567 checkMuteFromAppOpsNotification(new MyPlayer(mAt), aa); 568 } 569 570 @ApiTest(apis = {"android.media.AudioManager#getActivePlaybackConfigurations", 571 "android.media.AudioManager.AudioPlaybackCallback#onPlaybackConfigChanged", 572 "android.media.AudioManager.AudioPlaybackCallback#isMuted", 573 "android.media.AudioManager.AudioPlaybackCallback#getMutedBy"}) testMediaPlayerMuteFromAppOpsNotification()574 public void testMediaPlayerMuteFromAppOpsNotification() throws Exception { 575 if (isWatch()) { 576 Log.w(TAG, "Skip testMediaPlayerMuteFromAppOpsNotification for Wear"); 577 return; 578 } 579 if (!isValidPlatform("testMediaPlayerMuteFromAppOpsNotification")) return; 580 if (hasAudioSilentProperty()) { 581 Log.w(TAG, "Device has ro.audio.silent set, skipping " 582 + "testMediaPlayerMuteFromAppOpsNotification"); 583 return; 584 } 585 586 final AudioAttributes aa = TEST_AUDIO_ATTRIBUTES; 587 initializeMediaPlayer(aa); 588 checkMuteFromAppOpsNotification(new MyPlayer(mMp), aa); 589 } 590 checkMuteFromAppOpsNotification( @onNull MyPlayer player, @NonNull AudioAttributes aa)591 private void checkMuteFromAppOpsNotification( 592 @NonNull MyPlayer player, @NonNull AudioAttributes aa) throws Exception { 593 verifyMuteUnmuteNotifications( 594 /* start= */ player.mPlay, 595 /* mute= */ () -> { 596 try { 597 setOpMode(getContext().getPackageName(), OPSTR_PLAY_AUDIO, MODE_IGNORED); 598 } catch (IOException e) { 599 fail("Failed to set AppOps ignore for play audio: " + e); 600 } 601 }, 602 /* unmute= */ () -> { 603 try { 604 if (getOpMode(getContext().getPackageName(), OPSTR_PLAY_AUDIO) 605 != MODE_ALLOWED) { 606 setOpMode( 607 getContext().getPackageName(), OPSTR_PLAY_AUDIO, MODE_ALLOWED); 608 } 609 } catch (IOException e) { 610 fail("Failed to set AppOps allow for play audio: " + e); 611 } 612 }, 613 /* muteChangesActiveState= */ true, 614 MUTED_BY_APP_OPS, 615 player.mSessionId, 616 aa); 617 } 618 619 @ApiTest(apis = {"android.media.AudioManager#getActivePlaybackConfigurations", 620 "android.media.AudioManager.AudioPlaybackCallback#onPlaybackConfigChanged", 621 "android.media.AudioManager.AudioPlaybackCallback#isMuted", 622 "android.media.AudioManager.AudioPlaybackCallback#getMutedBy"}) testAudioTrackMuteFromStreamVolumeNotification()623 public void testAudioTrackMuteFromStreamVolumeNotification() throws Exception { 624 if (!isValidPlatform("testAudioTrackMuteFromStreamVolumeNotification")) return; 625 if (hasAudioSilentProperty()) { 626 Log.w(TAG, "Device has ro.audio.silent set, skipping " 627 + "testAudioTrackMuteFromStreamVolumeNotification"); 628 return; 629 } 630 631 final AudioAttributes aa = TEST_AUDIO_ATTRIBUTES; 632 initializeAudioTrack(aa); 633 checkMuteFromStreamVolumeNotification(new MyPlayer(mAt), aa); 634 } 635 636 @ApiTest(apis = {"android.media.AudioManager#getActivePlaybackConfigurations", 637 "android.media.AudioManager.AudioPlaybackCallback#onPlaybackConfigChanged", 638 "android.media.AudioManager.AudioPlaybackCallback#isMuted", 639 "android.media.AudioManager.AudioPlaybackCallback#getMutedBy"}) testMediaPlayerMuteFromStreamVolumeNotification()640 public void testMediaPlayerMuteFromStreamVolumeNotification() throws Exception { 641 if (!isValidPlatform("testMediaPlayerMuteFromStreamVolumeNotification")) return; 642 if (hasAudioSilentProperty()) { 643 Log.w(TAG, "Device has ro.audio.silent set, skipping " 644 + "testMediaPlayerMuteFromStreamVolumeNotification"); 645 return; 646 } 647 648 final AudioAttributes aa = TEST_AUDIO_ATTRIBUTES; 649 initializeMediaPlayer(aa); 650 checkMuteFromStreamVolumeNotification(new MyPlayer(mMp), aa); 651 } 652 checkMuteFromStreamVolumeNotification(MyPlayer player, @NonNull AudioAttributes aa)653 private void checkMuteFromStreamVolumeNotification(MyPlayer player, @NonNull AudioAttributes aa) 654 throws Exception { 655 AudioManager am = new AudioManager(getContext()); 656 assertNotNull("Could not create AudioManager", am); 657 658 if (am.isVolumeFixed()) { 659 Log.w(TAG, "Skipping testMuteFromStreamVolumeNotification, device has volume fixed."); 660 return; 661 } 662 663 verifyMuteUnmuteNotifications( 664 /* start= */ player.mPlay, 665 /* mute= */ () -> adjustMuteStreamVolume(am), 666 /* unmute= */ () -> adjustUnMuteStreamVolume(am), 667 /* muteChangesActiveState= */ false, 668 MUTED_BY_STREAM_VOLUME | MUTED_BY_PORT_VOLUME, 669 player.mSessionId, 670 aa); 671 } 672 673 @ApiTest(apis = {"android.media.AudioManager#getActivePlaybackConfigurations", 674 "android.media.AudioManager.AudioPlaybackCallback#onPlaybackConfigChanged", 675 "android.media.AudioManager.AudioPlaybackCallback#isMuted", 676 "android.media.AudioManager.AudioPlaybackCallback#getMutedBy"}) testAudioTrackMuteFromClientVolumeNotification()677 public void testAudioTrackMuteFromClientVolumeNotification() throws Exception { 678 if (!isValidPlatform("testAudioTrackMuteFromClientVolumeNotification")) return; 679 if (hasAudioSilentProperty()) { 680 Log.w(TAG, "Device has ro.audio.silent set, skipping " 681 + "testAudioTrackMuteFromClientVolumeNotification"); 682 return; 683 } 684 685 final AudioAttributes aa = TEST_AUDIO_ATTRIBUTES; 686 initializeAudioTrack(aa); 687 checkMuteFromClientVolumeNotification(new MyPlayer(mAt), aa); 688 } 689 690 @ApiTest(apis = {"android.media.AudioManager#getActivePlaybackConfigurations", 691 "android.media.AudioManager.AudioPlaybackCallback#onPlaybackConfigChanged", 692 "android.media.AudioManager.AudioPlaybackCallback#isMuted", 693 "android.media.AudioManager.AudioPlaybackCallback#getMutedBy"}) testMediaPlayerMuteFromClientVolumeNotification()694 public void testMediaPlayerMuteFromClientVolumeNotification() throws Exception { 695 if (!isValidPlatform("testMediaPlayerMuteFromClientVolumeNotification")) return; 696 if (hasAudioSilentProperty()) { 697 Log.w(TAG, "Device has ro.audio.silent set, skipping " 698 + "testMediaPlayerMuteFromClientVolumeNotification"); 699 return; 700 } 701 702 final AudioAttributes aa = TEST_AUDIO_ATTRIBUTES; 703 initializeMediaPlayer(aa); 704 checkMuteFromClientVolumeNotification(new MyPlayer(mMp), aa); 705 } 706 checkMuteFromClientVolumeNotification(MyPlayer player, @NonNull AudioAttributes aa)707 private void checkMuteFromClientVolumeNotification(MyPlayer player, @NonNull AudioAttributes aa) 708 throws Exception { 709 verifyMuteUnmuteNotifications( 710 /* start= */ player.mPlay, 711 /* mute= */ () -> player.mSetClientVolume.accept(0.f), 712 /* unmute= */ () -> player.mSetClientVolume.accept(1.f), 713 /* muteChangesActiveState= */ true, 714 MUTED_BY_CLIENT_VOLUME, 715 player.mSessionId, 716 aa); 717 } 718 719 @ApiTest(apis = {"android.media.AudioManager#getActivePlaybackConfigurations", 720 "android.media.AudioManager.AudioPlaybackCallback#onPlaybackConfigChanged", 721 "android.media.AudioManager.AudioPlaybackCallback#isMuted", 722 "android.media.AudioManager.AudioPlaybackCallback#getMutedBy"}) testAudioTrackMuteFromVolumeShaperNotification()723 public void testAudioTrackMuteFromVolumeShaperNotification() throws Exception { 724 if (!isValidPlatform("testAudioTrackMuteFromVolumeShaperNotification")) return; 725 if (hasAudioSilentProperty()) { 726 Log.w(TAG, "Device has ro.audio.silent set, skipping " 727 + "testAudioTrackMuteFromVolumeShaperNotification"); 728 return; 729 } 730 731 final AudioAttributes aa = TEST_AUDIO_ATTRIBUTES; 732 initializeAudioTrack(aa); 733 checkMuteFromVolumeShaperNotification(new MyPlayer(mAt), aa); 734 } 735 736 @ApiTest(apis = {"android.media.AudioManager#getActivePlaybackConfigurations", 737 "android.media.AudioManager.AudioPlaybackCallback#onPlaybackConfigChanged", 738 "android.media.AudioManager.AudioPlaybackCallback#isMuted", 739 "android.media.AudioManager.AudioPlaybackCallback#getMutedBy"}) testMediaPlayerMuteFromVolumeShaperNotification()740 public void testMediaPlayerMuteFromVolumeShaperNotification() throws Exception { 741 if (!isValidPlatform("testMediaPlayerMuteFromVolumeShaperNotification")) return; 742 if (hasAudioSilentProperty()) { 743 Log.w(TAG, "Device has ro.audio.silent set, skipping " 744 + "testMediaPlayerMuteFromVolumeShaperNotification"); 745 return; 746 } 747 748 final AudioAttributes aa = TEST_AUDIO_ATTRIBUTES; 749 initializeMediaPlayer(aa); 750 checkMuteFromVolumeShaperNotification(new MyPlayer(mMp), aa); 751 } 752 checkMuteFromVolumeShaperNotification(MyPlayer player, @NonNull AudioAttributes aa)753 private void checkMuteFromVolumeShaperNotification(MyPlayer player, @NonNull AudioAttributes aa) 754 throws Exception { 755 verifyMuteUnmuteNotifications( 756 /* start= */ player.mPlay, 757 /* mute= */ () -> { 758 mMuteShaper = player.mCreateVolumeShaper.apply(SHAPER_MUTE); 759 mMuteShaper.apply(VolumeShaper.Operation.PLAY); 760 }, 761 /* unmute= */ () -> { 762 mMuteShaper.replace( 763 SHAPER_MUTE, VolumeShaper.Operation.REVERSE, /* join= */ false); 764 mMuteShaper.apply(VolumeShaper.Operation.PLAY); 765 }, 766 /* muteChangesActiveState= */ true, 767 MUTED_BY_VOLUME_SHAPER, 768 player.mSessionId, 769 aa); 770 } 771 verifyMuteUnmuteNotifications( Runnable start, Runnable mute, Runnable unmute, boolean muteChangesActiveState, int checkFlag, int sessionId, @NonNull AudioAttributes aa)772 private void verifyMuteUnmuteNotifications( 773 Runnable start, 774 Runnable mute, 775 Runnable unmute, 776 boolean muteChangesActiveState, 777 int checkFlag, 778 int sessionId, 779 @NonNull AudioAttributes aa) 780 throws Exception { 781 AudioManager am = new AudioManager(getContext()); 782 assertNotNull("Could not create AudioManager", am); 783 if (isRingerModeSilent(am)) { 784 Log.w(TAG, "skipping verifyMuteUnmuteNotifications for RINGER_MODE_SILENT"); 785 return; 786 } 787 788 Thread.sleep(TEST_TIMING_TOLERANCE_MS + PLAY_ROUTING_TIMING_TOLERANCE_MS); 789 790 final MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback(sessionId, aa); 791 MyAudioPlaybackCallback registeredCallback = null; 792 793 try { 794 am.registerAudioPlaybackCallback(callback, null /*handler*/); 795 registeredCallback = callback; 796 797 // start playing audio 798 start.run(); 799 800 if (muteChangesActiveState) { 801 assertTrue("onPlaybackConfigChanged should have been called for " 802 + "PLAYER_STATE_STARTED, PLAYER_UPDATE_FORMAT, and PLAYER_UPDATE_DEVICE_ID", 803 callback.waitForCallbacks(3, 804 TEST_TIMING_TOLERANCE_MS + PLAY_ROUTING_TIMING_TOLERANCE_MS)); 805 } 806 807 Thread.sleep(TEST_TIMING_TOLERANCE_MS + PLAY_ROUTING_TIMING_TOLERANCE_MS); 808 809 // mute with Runnable 810 callback.reset(); 811 mute.run(); 812 813 if (muteChangesActiveState) { 814 assertTrue("onPlaybackConfigChanged for PLAYER_UPDATE_MUTED expected", 815 callback.waitForCallbacks(1, 816 TEST_TIMING_TOLERANCE_MS + PLAY_ROUTING_TIMING_TOLERANCE_MS)); 817 } else { 818 Thread.sleep(TEST_TIMING_TOLERANCE_MS + PLAY_ROUTING_TIMING_TOLERANCE_MS); 819 } 820 821 checkMutedApi(checkFlag, sessionId); 822 823 // unmute with Runnable 824 callback.reset(); 825 unmute.run(); 826 827 if (muteChangesActiveState) { 828 assertTrue("onPlaybackConfigChanged for PLAYER_UPDATE_MUTED expected after unmute", 829 callback.waitForCallbacks(1, 830 TEST_TIMING_TOLERANCE_MS + PLAY_ROUTING_TIMING_TOLERANCE_MS)); 831 } else { 832 Thread.sleep(TEST_TIMING_TOLERANCE_MS + PLAY_ROUTING_TIMING_TOLERANCE_MS); 833 } 834 } finally { 835 if (registeredCallback != null) { 836 am.unregisterAudioPlaybackCallback(registeredCallback); 837 } 838 unmute.run(); 839 } 840 } 841 adjustUnMuteStreamVolume(AudioManager am)842 private void adjustUnMuteStreamVolume(AudioManager am) { 843 try { 844 adoptShellPermissionIdentity(MODIFY_AUDIO_SETTINGS_PRIVILEGED); 845 am.adjustStreamVolume(TEST_STREAM_FOR_USAGE, ADJUST_UNMUTE, /* flags= */ 0); 846 } catch (Exception e) { 847 fail("Exception thrown during adjustStreamVolume unmute " + e); 848 } finally { 849 dropShellPermissionIdentity(); 850 } 851 } 852 adjustMuteStreamVolume(AudioManager am)853 private void adjustMuteStreamVolume(AudioManager am) { 854 try { 855 adoptShellPermissionIdentity(MODIFY_AUDIO_SETTINGS_PRIVILEGED); 856 am.adjustStreamVolume(TEST_STREAM_FOR_USAGE, ADJUST_MUTE, /* flags= */ 0); 857 } catch (Exception e) { 858 fail("Exception thrown during adjustStreamVolume mute " + e); 859 } finally { 860 dropShellPermissionIdentity(); 861 } 862 } 863 checkMutedApi(int checkFlag, int sessionId)864 private void checkMutedApi(int checkFlag, int sessionId) { 865 try { 866 adoptShellPermissionIdentity(Manifest.permission.MODIFY_AUDIO_ROUTING); 867 868 AudioPlaybackConfiguration currentConfiguration = 869 findConfiguration(checkFlag, sessionId); 870 assertTrue("APC should be muted", currentConfiguration.isMuted()); 871 assertTrue( 872 "APC muted by wrong source", 873 (currentConfiguration.getMutedBy() & checkFlag) != 0); 874 } finally { 875 dropShellPermissionIdentity(); 876 } 877 } 878 findConfiguration(int muteHint, int sessionId)879 private AudioPlaybackConfiguration findConfiguration(int muteHint, int sessionId) { 880 AudioManager am = new AudioManager(getContext()); 881 List<AudioPlaybackConfiguration> configList = am.getActivePlaybackConfigurations(); 882 AudioPlaybackConfiguration result = null; 883 for (AudioPlaybackConfiguration config : configList) { 884 if (config.getClientUid() == mUid 885 && config.getClientPid() == mPid 886 && config.getSessionId() == sessionId 887 && config.getAudioDeviceInfo() != null 888 && config.getAudioAttributes().getUsage() == TEST_USAGE 889 && config.getAudioAttributes().getContentType() == TEST_CONTENT) { 890 Log.v( 891 TAG, 892 "AudioPlaybackConfiguration " 893 + config 894 + " uid(" 895 + config.getClientUid() 896 + "/" 897 + mUid 898 + ") pid(" 899 + config.getClientPid() 900 + "/" 901 + mPid 902 + ") sessionId(" 903 + config.getSessionId() 904 + "/" 905 + sessionId 906 + ")"); 907 result = config; 908 if ((config.getMutedBy() & muteHint) != 0) { 909 break; 910 } 911 } 912 } 913 assertNotNull("Could not find AudioPlaybackConfiguration for uid " + mUid, result); 914 return result; 915 } 916 initializeAudioTrack(@onNull AudioAttributes aa)917 private void initializeAudioTrack(@NonNull AudioAttributes aa) { 918 final int bufferSizeInBytes = 919 TEST_AUDIO_TRACK_PLAY_SECONDS * TEST_AUDIO_TRACK_SAMPLERATE 920 * TEST_AUDIO_TRACK_CHANNELS * Short.BYTES; 921 922 ByteBuffer audioData = createSoundDataInShortByteBuffer( 923 bufferSizeInBytes / Short.BYTES, 924 TEST_AUDIO_TRACK_SAMPLERATE, TEST_AUDIO_TRACK_FREQUENCY, 925 TEST_AUDIO_TRACK_SWEEP); 926 927 mAt = new AudioTrack.Builder() 928 .setAudioAttributes(aa) 929 .setAudioFormat(new AudioFormat.Builder() 930 .setSampleRate(TEST_AUDIO_TRACK_SAMPLERATE) 931 .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) 932 .setEncoding(AudioFormat.ENCODING_PCM_16BIT) 933 .build()) 934 .setBufferSizeInBytes(bufferSizeInBytes) 935 .build(); 936 mAt.write(audioData, audioData.remaining(), WRITE_NON_BLOCKING); 937 } 938 initializeMediaPlayer(@onNull AudioAttributes aa)939 private void initializeMediaPlayer(@NonNull AudioAttributes aa) throws Exception { 940 AudioManager am = new AudioManager(getContext()); 941 assertNotNull("Could not create AudioManager", am); 942 943 mMp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, aa, am.generateAudioSessionId()); 944 } 945 946 @Nullable createPreparedMediaPlayer( @awRes int resID, @NonNull AudioAttributes aa, int session)947 private MediaPlayer createPreparedMediaPlayer( 948 @RawRes int resID, @NonNull AudioAttributes aa, int session) throws Exception { 949 final TestUtils.Monitor onPreparedCalled = new TestUtils.Monitor(); 950 final MediaPlayer mp = createPlayer(resID, aa, session); 951 mp.setOnPreparedListener(mp1 -> onPreparedCalled.signal()); 952 mp.prepare(); 953 onPreparedCalled.waitForSignal(MEDIAPLAYER_PREPARE_TIMEOUT_MS); 954 assertTrue( 955 "MediaPlayer wasn't prepared in under " + MEDIAPLAYER_PREPARE_TIMEOUT_MS + " ms", 956 onPreparedCalled.isSignalled()); 957 return mp; 958 } 959 createPlayer(@awRes int resID, @NonNull AudioAttributes aa, int session)960 private MediaPlayer createPlayer(@RawRes int resID, @NonNull AudioAttributes aa, int session) 961 throws IOException { 962 MediaPlayer mp = new MediaPlayer(); 963 mp.setAudioAttributes(aa); 964 mp.setAudioSessionId(session); 965 AssetFileDescriptor afd = getContext().getResources().openRawResourceFd(resID); 966 try { 967 mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); 968 } finally { 969 afd.close(); 970 } 971 return mp; 972 } 973 createSoundPool(int session, @NonNull AudioAttributes aa)974 private SoundPool createSoundPool(int session, @NonNull AudioAttributes aa) { 975 SoundPool sp = 976 new SoundPool.Builder() 977 .setAudioAttributes(aa) 978 .setMaxStreams(1) 979 .setAudioSessionId(session) 980 .build(); 981 return sp; 982 } 983 984 /** Loads a track and plays it with the passed {@link SoundPool}. */ playSoundPool(SoundPool sp, Context context)985 private static int playSoundPool(SoundPool sp, Context context) throws InterruptedException { 986 final Object loadLock = new Object(); 987 assertNotNull("SoundPool must not be NULL", sp); 988 final SoundPool zepool = sp; 989 AtomicBoolean spLoaded = new AtomicBoolean(false); 990 // load a sound and play it once load completion is reported 991 sp.setOnLoadCompleteListener( 992 new SoundPool.OnLoadCompleteListener() { 993 @Override 994 public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { 995 assertEquals("Receiving load completion for wrong SoundPool", zepool, sp); 996 assertEquals("Load completion error", 0 /*success expected*/, status); 997 synchronized (loadLock) { 998 spLoaded.set(true); 999 loadLock.notify(); 1000 } 1001 } 1002 }); 1003 final int loadId = sp.load(context, R.raw.sine1320hz5sec, 1/*priority*/); 1004 synchronized (loadLock) { 1005 while (!spLoaded.get()) { 1006 loadLock.wait(TEST_TIMEOUT_SOUNDPOOL_LOAD_MS); 1007 } 1008 } 1009 1010 int res = sp.play(loadId, 1.0f /*leftVolume*/, 1.0f /*rightVolume*/, 1 /*priority*/, 1011 0 /*loop*/, 1.0f/*rate*/); 1012 // FIXME SoundPool activity is not reported yet, but exercise creation/release with 1013 // an AudioPlaybackCallback registered 1014 assertTrue("Error playing sound through SoundPool", res > 0); 1015 return res; 1016 } 1017 stopSoundPool(SoundPool sp, final int streamId)1018 private static void stopSoundPool(SoundPool sp, final int streamId) { 1019 if (sp != null) { 1020 sp.stop(streamId); 1021 } 1022 } 1023 1024 @GuardedBy("mMediaPlayerLock") waitAddedPlayerInStateWithCallback( @onNull MyAudioPlaybackCallback callback, int playerNumber, int state)1025 private void waitAddedPlayerInStateWithCallback( 1026 @NonNull MyAudioPlaybackCallback callback, int playerNumber, int state) { 1027 boolean waitSuccess = 1028 mMediaPlayerLock.waitFor( 1029 TEST_TIMING_TOLERANCE_MS, 1030 () -> callback.getMediaPlayerConfigsNumberInState(state) == playerNumber); 1031 1032 assertTrue( 1033 "Must got " + playerNumber + " active MediaPlayer. " + callback.debugString(), 1034 waitSuccess); 1035 } 1036 1037 @GuardedBy("mMediaPlayerLock") waitAddedActivePlayerWithAudioManager( @onNull AudioManager am, @NonNull AudioAttributes aa, int sessionId, final Set<AudioPlaybackConfiguration> oldConfigs, int playerNumber)1038 private void waitAddedActivePlayerWithAudioManager( 1039 @NonNull AudioManager am, 1040 @NonNull AudioAttributes aa, 1041 int sessionId, 1042 final Set<AudioPlaybackConfiguration> oldConfigs, 1043 int playerNumber) { 1044 if (playerNumber > 0) { 1045 boolean waitSuccess = 1046 mMediaPlayerLock.waitFor( 1047 TEST_TIMING_TOLERANCE_MS, 1048 () -> 1049 getAddedPlayerConfigsNumber(am, oldConfigs, sessionId, aa) 1050 == playerNumber); 1051 assertTrue("Must got " + playerNumber + " active MediaPlayer.", waitSuccess); 1052 } 1053 } 1054 1055 @GuardedBy("mMediaPlayerLock") startMediaPlayer(final @NonNull MediaPlayer mp)1056 private boolean startMediaPlayer(final @NonNull MediaPlayer mp) { 1057 mp.start(); 1058 return mMediaPlayerLock.waitFor(TEST_TIMING_TOLERANCE_MS, () -> mp.isPlaying()); 1059 } 1060 1061 @GuardedBy("mMediaPlayerLock") stopMediaPlayer(final MediaPlayer mp)1062 private boolean stopMediaPlayer(final MediaPlayer mp) { 1063 mp.stop(); 1064 return mMediaPlayerLock.waitFor(TEST_TIMING_TOLERANCE_MS, () -> !mp.isPlaying()); 1065 } 1066 1067 @GuardedBy("mMediaPlayerLock") pauseMediaPlayer(final MediaPlayer mp)1068 private boolean pauseMediaPlayer(final MediaPlayer mp) { 1069 mp.pause(); 1070 return mMediaPlayerLock.waitFor(TEST_TIMING_TOLERANCE_MS, () -> !mp.isPlaying()); 1071 } 1072 1073 /** 1074 * Start MediaPlayer with AudioPlaybackConfigurations check, after player start successfully: - 1075 * The number of matched AudioManager.getActivePlaybackConfigurations must at least increase 1076 * exactly by 1 - If AudioPlaybackCallback registered, number of matched 1077 * AudioPlaybackConfiguration must increase exactly by 1 1078 */ startMediaPlayerWithCheck( @onNull AudioManager am, @NonNull MediaPlayer mp, @NonNull AudioAttributes aa, int sessionId, final MyAudioPlaybackCallback callback)1079 private void startMediaPlayerWithCheck( 1080 @NonNull AudioManager am, 1081 @NonNull MediaPlayer mp, 1082 @NonNull AudioAttributes aa, 1083 int sessionId, 1084 final MyAudioPlaybackCallback callback) 1085 throws Exception { 1086 synchronized (mMediaPlayerLock) { 1087 assertFalse("MediaPlayer already started", mp.isPlaying()); 1088 1089 final Set<AudioPlaybackConfiguration> oldConfigs = 1090 new HashSet<AudioPlaybackConfiguration>(am.getActivePlaybackConfigurations()); 1091 if (callback != null) { 1092 callback.reset(); 1093 } 1094 1095 assertTrue("MediaPlayer start failed", startMediaPlayer(mp)); 1096 1097 if (callback != null) { 1098 waitAddedPlayerInStateWithCallback( 1099 callback, 1, AudioPlaybackConfiguration.PLAYER_STATE_STARTED); 1100 assertTrue( 1101 "onPlaybackConfigChanged PLAYER_STATE_STARTED, PLAYER_UPDATE_FORMAT, and " 1102 + "PLAYER_UPDATE_DEVICE_ID events expected", 1103 callback.waitForCallbacks( 1104 3, TEST_TIMING_TOLERANCE_MS + PLAY_ROUTING_TIMING_TOLERANCE_MS)); 1105 assertTrue( 1106 "onPlaybackConfigChanged should have been called at least once", 1107 mMediaPlayerLock.waitFor( 1108 TEST_TIMING_TOLERANCE_MS, 1109 () -> callback.getCbInvocationNumber() >= 1)); 1110 final List<AudioPlaybackConfiguration> configs = callback.getMediaPlayerConfigs(); 1111 assertTrue( 1112 "Active player, attributes " 1113 + aa 1114 + " not expected in policy " 1115 + configs.get(0).getAudioAttributes().getAllowedCapturePolicy() 1116 + " vs " 1117 + aa.getAllowedCapturePolicy(), 1118 hasAttr(configs, aa)); 1119 } 1120 1121 // active MediaPlayer, number of configs should increase by 1 1122 waitAddedActivePlayerWithAudioManager(am, aa, sessionId, oldConfigs, 1); 1123 } 1124 } 1125 1126 @GuardedBy("mMediaPlayerLock") pauseOrStopMediaPlayerCheck(final MyAudioPlaybackCallback callback)1127 private void pauseOrStopMediaPlayerCheck(final MyAudioPlaybackCallback callback) 1128 throws Exception { 1129 1130 if (callback != null) { 1131 assertNotNull("Callback must not be NULL", callback); 1132 assertTrue( 1133 "onPlaybackConfigChanged should have been called " + callback.debugString(), 1134 mMediaPlayerLock.waitFor( 1135 TEST_TIMING_TOLERANCE_MS, () -> callback.getCbInvocationNumber() >= 1)); 1136 waitAddedPlayerInStateWithCallback( 1137 callback, 0, AudioPlaybackConfiguration.PLAYER_STATE_STARTED); 1138 } 1139 } 1140 pauseMediaPlayerWithCheck( @onNull AudioManager am, @NonNull MediaPlayer mp, @NonNull AudioAttributes aa, int sessionId, final MyAudioPlaybackCallback callback)1141 private void pauseMediaPlayerWithCheck( 1142 @NonNull AudioManager am, 1143 @NonNull MediaPlayer mp, 1144 @NonNull AudioAttributes aa, 1145 int sessionId, 1146 final MyAudioPlaybackCallback callback) 1147 throws Exception { 1148 synchronized (mMediaPlayerLock) { 1149 assertTrue("MediaPlayer should have started", mp.isPlaying()); 1150 if (callback != null) { 1151 callback.reset(); 1152 } 1153 final Set<AudioPlaybackConfiguration> oldConfigs = 1154 new HashSet<AudioPlaybackConfiguration>(am.getActivePlaybackConfigurations()); 1155 assertTrue("MediaPlayer pause failed", pauseMediaPlayer(mMp)); 1156 pauseOrStopMediaPlayerCheck(callback); 1157 } 1158 } 1159 stopMediaPlayerWithCheck( @onNull AudioManager am, @NonNull MediaPlayer mp, @NonNull AudioAttributes aa, int sessionId, final MyAudioPlaybackCallback callback)1160 private void stopMediaPlayerWithCheck( 1161 @NonNull AudioManager am, 1162 @NonNull MediaPlayer mp, 1163 @NonNull AudioAttributes aa, 1164 int sessionId, 1165 final MyAudioPlaybackCallback callback) 1166 throws Exception { 1167 synchronized (mMediaPlayerLock) { 1168 assertTrue("MediaPlayer should have started", mp.isPlaying()); 1169 if (callback != null) { 1170 callback.reset(); 1171 } 1172 final Set<AudioPlaybackConfiguration> oldConfigs = 1173 new HashSet<AudioPlaybackConfiguration>(am.getActivePlaybackConfigurations()); 1174 assertTrue("MediaPlayer stop failed", stopMediaPlayer(mMp)); 1175 pauseOrStopMediaPlayerCheck(callback); 1176 } 1177 } 1178 1179 private class MyAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback { 1180 private final Object mCbLock = new Object(); 1181 1182 @GuardedBy("mCbLock") 1183 private int mCalled; 1184 1185 @GuardedBy("mCbLock") 1186 private List<AudioPlaybackConfiguration> mConfigs; 1187 1188 @GuardedBy("mCbLock") 1189 private int mSessionId = AudioSystem.AUDIO_SESSION_ALLOCATE; 1190 1191 @GuardedBy("mCbLock") 1192 private AudioAttributes mAudioAttributes = TEST_AUDIO_ATTRIBUTES; 1193 1194 final TestUtils.Monitor mOnCalledMonitor = new TestUtils.Monitor(); 1195 reset()1196 void reset() { 1197 synchronized (mCbLock) { 1198 mCalled = 0; 1199 mConfigs = new ArrayList<AudioPlaybackConfiguration>(); 1200 } 1201 mOnCalledMonitor.reset(); 1202 } 1203 getCbInvocationNumber()1204 int getCbInvocationNumber() { 1205 synchronized (mCbLock) { 1206 return mCalled; 1207 } 1208 } 1209 getMediaPlayerConfigs()1210 List<AudioPlaybackConfiguration> getMediaPlayerConfigs() { 1211 synchronized (mCbLock) { 1212 return mConfigs; 1213 } 1214 } 1215 getMediaPlayerConfigsNumberInState(int state)1216 int getMediaPlayerConfigsNumberInState(int state) { 1217 return getMediaPlayerConfigs().stream() 1218 .filter(config -> config.getPlayerState() == state) 1219 .collect(Collectors.toList()) 1220 .size(); 1221 } 1222 MyAudioPlaybackCallback(int session, @NonNull AudioAttributes attributes)1223 MyAudioPlaybackCallback(int session, @NonNull AudioAttributes attributes) { 1224 mAudioAttributes = attributes; 1225 mSessionId = session; 1226 reset(); 1227 } 1228 1229 @GuardedBy("mCbLock") filterConfigsWithCurrentPlayer( final List<AudioPlaybackConfiguration> configs)1230 private List<AudioPlaybackConfiguration> filterConfigsWithCurrentPlayer( 1231 final List<AudioPlaybackConfiguration> configs) { 1232 List<AudioPlaybackConfiguration> result = new ArrayList<AudioPlaybackConfiguration>(); 1233 for (final AudioPlaybackConfiguration config : configs) { 1234 try { 1235 if (isPlayerConfigMatches(config, mSessionId, mAudioAttributes)) { 1236 result.add(config); 1237 } 1238 } catch (Exception e) { 1239 fail("Exception thrown during reflection on config privileged fields" + e); 1240 } 1241 } 1242 return result; 1243 } 1244 1245 @Override onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs)1246 public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { 1247 synchronized (mCbLock) { 1248 mConfigs = filterConfigsWithCurrentPlayer(configs); 1249 // add counter and signal when there is a matching AudioPlaybackConfiguration 1250 if (!mIsPrivileged || mConfigs.size() != 0) { 1251 mCalled++; 1252 mOnCalledMonitor.signal(); 1253 } 1254 } 1255 } 1256 waitForCallbacks(int calledCount, long timeoutMs)1257 public boolean waitForCallbacks(int calledCount, long timeoutMs) 1258 throws InterruptedException { 1259 int signalsCounted = mOnCalledMonitor.waitForCountedSignals(calledCount, timeoutMs); 1260 return (signalsCounted >= calledCount); 1261 } 1262 debugString()1263 public String debugString() { 1264 synchronized (mCbLock) { 1265 String debug = mIsPrivileged ? "Privileged" : "NonPrivileged"; 1266 debug += " SessionId: " + Integer.toString(mSessionId); 1267 debug += " Configs: " + mConfigs.toString(); 1268 return debug; 1269 } 1270 } 1271 } 1272 1273 private static class MyPlayer { 1274 final Runnable mPlay; 1275 final Consumer<Float> mSetClientVolume; 1276 final Function<VolumeShaper.Configuration, VolumeShaper> mCreateVolumeShaper; 1277 final int mSessionId; 1278 MyPlayer(AudioTrack at)1279 MyPlayer(AudioTrack at) { 1280 mPlay = at::play; 1281 mSetClientVolume = at::setVolume; 1282 mCreateVolumeShaper = at::createVolumeShaper; 1283 mSessionId = at.getAudioSessionId(); 1284 } 1285 MyPlayer(MediaPlayer mp)1286 MyPlayer(MediaPlayer mp) { 1287 mPlay = mp::start; 1288 mSetClientVolume = mp::setVolume; 1289 mCreateVolumeShaper = mp::createVolumeShaper; 1290 mSessionId = mp.getAudioSessionId(); 1291 } 1292 } 1293 hasAttr(List<AudioPlaybackConfiguration> configs, AudioAttributes aa)1294 private static boolean hasAttr(List<AudioPlaybackConfiguration> configs, AudioAttributes aa) { 1295 for (AudioPlaybackConfiguration apc : configs) { 1296 if (apc.getAudioAttributes().getContentType() == aa.getContentType() 1297 && apc.getAudioAttributes().getUsage() == aa.getUsage() 1298 && apc.getAudioAttributes().getFlags() == aa.getFlags() 1299 && anonymizeCapturePolicy(apc.getAudioAttributes().getAllowedCapturePolicy()) 1300 == anonymizeCapturePolicy(aa.getAllowedCapturePolicy())) { 1301 return true; 1302 } 1303 } 1304 return false; 1305 } 1306 hasDevice(List<AudioPlaybackConfiguration> configs, AudioAttributes aa, boolean enableRoutedDeviceIdsFlag)1307 private static boolean hasDevice(List<AudioPlaybackConfiguration> configs, AudioAttributes aa, 1308 boolean enableRoutedDeviceIdsFlag) { 1309 for (AudioPlaybackConfiguration apc : configs) { 1310 if (apc.getAudioAttributes().getContentType() == aa.getContentType() 1311 && apc.getAudioAttributes().getUsage() == aa.getUsage() 1312 && apc.getAudioAttributes().getFlags() == aa.getFlags() 1313 && anonymizeCapturePolicy(apc.getAudioAttributes().getAllowedCapturePolicy()) 1314 == aa.getAllowedCapturePolicy() 1315 && apc.getAudioDeviceInfo() != null 1316 && (!enableRoutedDeviceIdsFlag || apc.getAudioDeviceInfos().size() > 0)) { 1317 return true; 1318 } 1319 } 1320 return false; 1321 } 1322 1323 /** ALLOW_CAPTURE_BY_SYSTEM is anonymized to ALLOW_CAPTURE_BY_NONE. */ 1324 @CapturePolicy anonymizeCapturePolicy(@apturePolicy int policy)1325 private static int anonymizeCapturePolicy(@CapturePolicy int policy) { 1326 if (policy == ALLOW_CAPTURE_BY_SYSTEM) { 1327 return ALLOW_CAPTURE_BY_NONE; 1328 } 1329 return policy; 1330 } 1331 isValidPlatform(String testName)1332 private boolean isValidPlatform(String testName) { 1333 if (!getContext().getPackageManager() 1334 .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) { 1335 Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid " 1336 + "audio output HAL, skipping test " + testName); 1337 return false; 1338 } 1339 return true; 1340 } 1341 isWatch()1342 private boolean isWatch() { 1343 return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); 1344 } 1345 adoptShellPermissionIdentity(String permission)1346 private void adoptShellPermissionIdentity(String permission) { 1347 getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(permission); 1348 mIsPrivileged = true; 1349 } 1350 dropShellPermissionIdentity()1351 private void dropShellPermissionIdentity() { 1352 if (mIsPrivileged) { 1353 getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 1354 mIsPrivileged = false; 1355 } 1356 } 1357 1358 /** 1359 * For privileged player, compare sessionId. For non-privileged player, compare usage, 1360 * contentType, and flags in AudioAttributes. 1361 */ isPlayerConfigMatches( final AudioPlaybackConfiguration config, int sessionId, @NonNull final AudioAttributes oldAa)1362 private boolean isPlayerConfigMatches( 1363 final AudioPlaybackConfiguration config, 1364 int sessionId, 1365 @NonNull final AudioAttributes oldAa) { 1366 if (mIsPrivileged) { 1367 return config.getSessionId() == sessionId; 1368 } else { 1369 final AudioAttributes newAa = config.getAudioAttributes(); 1370 return newAa.getUsage() == oldAa.getUsage() 1371 && newAa.getContentType() == oldAa.getContentType() 1372 && newAa.getFlags() == oldAa.getFlags(); 1373 } 1374 } 1375 getAddedPlayerConfigsNumber( @onNull AudioManager am, final Set<AudioPlaybackConfiguration> oldConfigs, int sessionId, @NonNull AudioAttributes aa)1376 private int getAddedPlayerConfigsNumber( 1377 @NonNull AudioManager am, 1378 final Set<AudioPlaybackConfiguration> oldConfigs, 1379 int sessionId, 1380 @NonNull AudioAttributes aa) { 1381 return getAddedPlayerConfigs( 1382 oldConfigs, am.getActivePlaybackConfigurations(), sessionId, aa) 1383 .size(); 1384 } 1385 getAddedPlayerConfigs( final Set<AudioPlaybackConfiguration> oldConfigs, final List<AudioPlaybackConfiguration> newConfigs, int sessionId, @NonNull final AudioAttributes oldAa)1386 private List<AudioPlaybackConfiguration> getAddedPlayerConfigs( 1387 final Set<AudioPlaybackConfiguration> oldConfigs, 1388 final List<AudioPlaybackConfiguration> newConfigs, 1389 int sessionId, 1390 @NonNull final AudioAttributes oldAa) { 1391 List<AudioPlaybackConfiguration> addedConfigs = new ArrayList<AudioPlaybackConfiguration>(); 1392 1393 for (final AudioPlaybackConfiguration config : newConfigs) { 1394 if (!oldConfigs.contains(config) && isPlayerConfigMatches(config, sessionId, oldAa)) { 1395 addedConfigs.add(config); 1396 } 1397 } 1398 1399 return addedConfigs; 1400 } 1401 isRingerModeSilent(AudioManager am)1402 private boolean isRingerModeSilent(AudioManager am) { 1403 if (am == null) { 1404 am = new AudioManager(getContext()); 1405 } 1406 assertNotNull("Could not create AudioManager", am); 1407 1408 return am.getRingerMode() == AudioManager.RINGER_MODE_SILENT; 1409 } 1410 } 1411