• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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