1 /*
2  * Copyright (C) 2015 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 android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.media.AudioAttributes;
22 import android.media.AudioDeviceInfo;
23 import android.media.AudioFormat;
24 import android.media.AudioManager;
25 import android.media.AudioRecord;
26 import android.media.AudioRouting;
27 import android.media.AudioTrack;
28 import android.media.MediaFormat;
29 import android.media.MediaPlayer;
30 import android.media.MediaRecorder;
31 import android.media.audio.cts.R;
32 import android.media.cts.DeviceUtils;
33 import android.os.Handler;
34 import android.os.Looper;
35 import android.os.SystemClock;
36 import android.platform.test.annotations.AppModeFull;
37 import android.test.AndroidTestCase;
38 import android.util.Log;
39 
40 import com.android.compatibility.common.util.MediaUtils;
41 
42 import java.io.File;
43 import java.util.Arrays;
44 import java.util.HashSet;
45 import java.util.Set;
46 import java.util.concurrent.CountDownLatch;
47 import java.util.concurrent.TimeUnit;
48 
49 /**
50  * AudioTrack / AudioRecord / MediaPlayer / MediaRecorder preferred device
51  * and routing listener tests.
52  * The routing tests are mostly here to exercise the routing code, as an actual test would require
53  * adding / removing an audio device for the listeners to be called.
54  * The routing listener code is designed to run for two versions of the routing code:
55  *  - the deprecated AudioTrack.OnRoutingChangedListener and AudioRecord.OnRoutingChangedListener
56  *  - the N AudioRouting.OnRoutingChangedListener
57  */
58 @AppModeFull(reason = "TODO: evaluate and port to instant")
59 public class RoutingTest extends AndroidTestCase {
60     private static final String TAG = "RoutingTest";
61     private static final long WAIT_ROUTING_CHANGE_TIME_MS = 3000;
62     private static final int AUDIO_BIT_RATE_IN_BPS = 12200;
63     private static final int AUDIO_SAMPLE_RATE_HZ = 8000;
64     private static final long MAX_FILE_SIZE_BYTE = 5000;
65     private static final int RECORD_TIME_MS = 3000;
66     private static final long WAIT_PLAYBACK_START_TIME_MS = 1000;
67     private static final Set<Integer> AVAILABLE_INPUT_DEVICES_TYPE = new HashSet<>(
68         Arrays.asList(AudioDeviceInfo.TYPE_BUILTIN_MIC));
69 
70     private AudioManager mAudioManager;
71     private File mOutFile;
72 
73     @Override
setUp()74     protected void setUp() throws Exception {
75         super.setUp();
76 
77         // get the AudioManager
78         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
79         assertNotNull(mAudioManager);
80     }
81 
82     @Override
tearDown()83     protected void tearDown() throws Exception {
84         if (mOutFile != null && mOutFile.exists()) {
85             mOutFile.delete();
86         }
87         super.tearDown();
88     }
89 
allocAudioTrack()90     private AudioTrack allocAudioTrack() {
91         int bufferSize =
92                 AudioTrack.getMinBufferSize(
93                     41000,
94                     AudioFormat.CHANNEL_OUT_STEREO,
95                     AudioFormat.ENCODING_PCM_16BIT);
96         AudioTrack audioTrack =
97             new AudioTrack(
98                 AudioManager.STREAM_MUSIC,
99                 41000,
100                 AudioFormat.CHANNEL_OUT_STEREO,
101                 AudioFormat.ENCODING_PCM_16BIT,
102                 bufferSize,
103                 AudioTrack.MODE_STREAM);
104         return audioTrack;
105     }
106 
test_audioTrack_preferredDevice()107     public void test_audioTrack_preferredDevice() {
108         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
109             // Can't do it so skip this test
110             return;
111         }
112 
113         AudioTrack audioTrack = allocAudioTrack();
114         assertNotNull(audioTrack);
115 
116         // None selected (new AudioTrack), so check for default
117         assertNull(audioTrack.getPreferredDevice());
118 
119         // resets to default
120         assertTrue(audioTrack.setPreferredDevice(null));
121 
122         // test each device
123         AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
124         for (int index = 0; index < deviceList.length; index++) {
125             if (deviceList[index].getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
126                 // Device with type as TYPE_TELEPHONY requires a privileged permission.
127                 continue;
128             }
129             assertTrue(audioTrack.setPreferredDevice(deviceList[index]));
130             assertTrue(audioTrack.getPreferredDevice() == deviceList[index]);
131         }
132 
133         // Check defaults again
134         assertTrue(audioTrack.setPreferredDevice(null));
135         assertNull(audioTrack.getPreferredDevice());
136 
137         audioTrack.release();
138     }
139 
test_audioTrack_incallMusicRoutingPermissions()140     public void test_audioTrack_incallMusicRoutingPermissions() {
141         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
142             // Can't do it so skip this test
143             return;
144         }
145 
146         // only apps with MODIFY_PHONE_STATE permission can route playback
147         // to the uplink stream during a phone call, so this test makes sure that
148         // audio is re-routed to default device when the permission is missing
149 
150         AudioDeviceInfo telephonyDevice = getTelephonyDeviceAndSetInCommunicationMode();
151         if (telephonyDevice == null) {
152             // Can't do it so skip this test
153             return;
154         }
155 
156         AudioTrack audioTrack = null;
157 
158         try {
159             audioTrack = allocAudioTrack();
160             assertNotNull(audioTrack);
161 
162             audioTrack.setPreferredDevice(telephonyDevice);
163             assertEquals(AudioDeviceInfo.TYPE_TELEPHONY, audioTrack.getPreferredDevice().getType());
164 
165             audioTrack.play();
166             assertTrue(audioTrack.getRoutedDevice().getType() != AudioDeviceInfo.TYPE_TELEPHONY);
167 
168         } finally {
169             if (audioTrack != null) {
170                 audioTrack.stop();
171                 audioTrack.release();
172             }
173             mAudioManager.setMode(AudioManager.MODE_NORMAL);
174         }
175     }
176 
getTelephonyDeviceAndSetInCommunicationMode()177     private AudioDeviceInfo getTelephonyDeviceAndSetInCommunicationMode() {
178         // get the output device for telephony
179         AudioDeviceInfo telephonyDevice = null;
180         AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
181         for (int index = 0; index < deviceList.length; index++) {
182             if (deviceList[index].getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
183                 telephonyDevice = deviceList[index];
184             }
185         }
186 
187         if (telephonyDevice == null) {
188             return null;
189         }
190 
191         // simulate an in call state using MODE_IN_COMMUNICATION since
192         // AudioManager.setMode requires MODIFY_PHONE_STATE permission
193         // for setMode with MODE_IN_CALL.
194         mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
195         assertEquals(AudioManager.MODE_IN_COMMUNICATION, mAudioManager.getMode());
196 
197         return telephonyDevice;
198     }
199 
200     /*
201      * tests if the Looper for the current thread has been prepared,
202      * If not, it makes one, prepares it and returns it.
203      * If this returns non-null, the caller is reponsible for calling quit()
204      * on the returned Looper.
205      */
prepareIfNeededLooper()206     private Looper prepareIfNeededLooper() {
207         // non-null Handler
208         Looper myLooper = null;
209         if (Looper.myLooper() == null) {
210             Looper.prepare();
211             myLooper = Looper.myLooper();
212             assertNotNull(myLooper);
213         }
214         return myLooper;
215     }
216 
217     private class AudioTrackRoutingListener implements AudioTrack.OnRoutingChangedListener,
218             AudioRouting.OnRoutingChangedListener
219     {
onRoutingChanged(AudioTrack audioTrack)220         public void onRoutingChanged(AudioTrack audioTrack) {}
onRoutingChanged(AudioRouting audioRouting)221         public void onRoutingChanged(AudioRouting audioRouting) {}
222     }
223 
224 
test_audioTrack_RoutingListener()225     public void test_audioTrack_RoutingListener() {
226         test_audioTrack_RoutingListener(false /*usesAudioRouting*/);
227     }
228 
test_audioTrack_audioRouting_RoutingListener()229     public void test_audioTrack_audioRouting_RoutingListener() {
230         test_audioTrack_RoutingListener(true /*usesAudioRouting*/);
231     }
232 
test_audioTrack_RoutingListener(boolean usesAudioRouting)233     private void test_audioTrack_RoutingListener(boolean usesAudioRouting) {
234         AudioTrack audioTrack = allocAudioTrack();
235 
236         // null listener
237         if (usesAudioRouting) {
238             audioTrack.addOnRoutingChangedListener(
239                     (AudioRouting.OnRoutingChangedListener) null, null);
240         } else {
241             audioTrack.addOnRoutingChangedListener(
242                     (AudioTrack.OnRoutingChangedListener) null, null);
243         }
244 
245         AudioTrackRoutingListener listener = new AudioTrackRoutingListener();
246         AudioTrackRoutingListener someOtherListener = new AudioTrackRoutingListener();
247 
248         // add a listener
249         if (usesAudioRouting) {
250             audioTrack.addOnRoutingChangedListener(
251                     (AudioRouting.OnRoutingChangedListener) listener, null);
252         } else {
253             audioTrack.addOnRoutingChangedListener(listener, null);
254         }
255 
256         // remove listeners
257         if (usesAudioRouting) {
258             // remove a listener we didn't add
259             audioTrack.removeOnRoutingChangedListener(
260                     (AudioRouting.OnRoutingChangedListener) someOtherListener);
261             // remove a valid listener
262             audioTrack.removeOnRoutingChangedListener(
263                     (AudioRouting.OnRoutingChangedListener) listener);
264         } else {
265             // remove a listener we didn't add
266             audioTrack.removeOnRoutingChangedListener(
267                     (AudioTrack.OnRoutingChangedListener) someOtherListener);
268             // remove a valid listener
269             audioTrack.removeOnRoutingChangedListener(
270                     (AudioTrack.OnRoutingChangedListener) listener);
271         }
272 
273         Looper myLooper = prepareIfNeededLooper();
274 
275         if (usesAudioRouting) {
276             audioTrack.addOnRoutingChangedListener(
277                     (AudioRouting.OnRoutingChangedListener) listener, new Handler());
278             audioTrack.removeOnRoutingChangedListener(
279                     (AudioRouting.OnRoutingChangedListener) listener);
280         } else {
281             audioTrack.addOnRoutingChangedListener(
282                     (AudioTrack.OnRoutingChangedListener) listener, new Handler());
283             audioTrack.removeOnRoutingChangedListener(
284                     (AudioTrack.OnRoutingChangedListener) listener);
285         }
286 
287         audioTrack.release();
288         if (myLooper != null) {
289             myLooper.quit();
290         }
291    }
292 
allocAudioRecord()293     private AudioRecord allocAudioRecord() {
294         int bufferSize =
295                 AudioRecord.getMinBufferSize(
296                     41000,
297                     AudioFormat.CHANNEL_OUT_DEFAULT,
298                     AudioFormat.ENCODING_PCM_16BIT);
299         AudioRecord audioRecord =
300             new AudioRecord(
301                 MediaRecorder.AudioSource.DEFAULT,
302                 41000, AudioFormat.CHANNEL_OUT_DEFAULT,
303                 AudioFormat.ENCODING_PCM_16BIT,
304                 bufferSize);
305         return audioRecord;
306     }
307 
308     private class AudioRecordRoutingListener implements AudioRecord.OnRoutingChangedListener,
309             AudioRouting.OnRoutingChangedListener
310     {
onRoutingChanged(AudioRecord audioRecord)311         public void onRoutingChanged(AudioRecord audioRecord) {}
onRoutingChanged(AudioRouting audioRouting)312         public void onRoutingChanged(AudioRouting audioRouting) {}
313     }
314 
test_audioRecord_RoutingListener()315     public void test_audioRecord_RoutingListener() {
316         test_audioRecord_RoutingListener(false /*usesAudioRouting*/);
317     }
318 
test_audioRecord_audioRouting_RoutingListener()319     public void test_audioRecord_audioRouting_RoutingListener() {
320         test_audioRecord_RoutingListener(true /*usesAudioRouting*/);
321     }
322 
test_audioRecord_RoutingListener(boolean usesAudioRouting)323     private void test_audioRecord_RoutingListener(boolean usesAudioRouting) {
324         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
325             // Can't do it so skip this test
326             return;
327         }
328         AudioRecord audioRecord = allocAudioRecord();
329 
330         // null listener
331         if (usesAudioRouting) {
332             audioRecord.addOnRoutingChangedListener(
333                     (AudioRouting.OnRoutingChangedListener) null, null);
334         } else {
335             audioRecord.addOnRoutingChangedListener(
336                     (AudioRecord.OnRoutingChangedListener) null, null);
337         }
338 
339         AudioRecordRoutingListener listener = new AudioRecordRoutingListener();
340         AudioRecordRoutingListener someOtherListener = new AudioRecordRoutingListener();
341 
342         // add a listener
343         if (usesAudioRouting) {
344             audioRecord.addOnRoutingChangedListener(
345                     (AudioRouting.OnRoutingChangedListener) listener, null);
346         } else {
347             audioRecord.addOnRoutingChangedListener(
348                     (AudioRecord.OnRoutingChangedListener) listener, null);
349         }
350 
351         // remove listeners
352         if (usesAudioRouting) {
353             // remove a listener we didn't add
354             audioRecord.removeOnRoutingChangedListener(
355                     (AudioRouting.OnRoutingChangedListener) someOtherListener);
356             // remove a valid listener
357             audioRecord.removeOnRoutingChangedListener(
358                     (AudioRouting.OnRoutingChangedListener) listener);
359         } else {
360             // remove a listener we didn't add
361             audioRecord.removeOnRoutingChangedListener(
362                     (AudioRecord.OnRoutingChangedListener) someOtherListener);
363             // remove a valid listener
364             audioRecord.removeOnRoutingChangedListener(
365                     (AudioRecord.OnRoutingChangedListener) listener);
366         }
367 
368         Looper myLooper = prepareIfNeededLooper();
369         if (usesAudioRouting) {
370             audioRecord.addOnRoutingChangedListener(
371                     (AudioRouting.OnRoutingChangedListener) listener, new Handler());
372             audioRecord.removeOnRoutingChangedListener(
373                     (AudioRouting.OnRoutingChangedListener) listener);
374         } else {
375             audioRecord.addOnRoutingChangedListener(
376                     (AudioRecord.OnRoutingChangedListener) listener, new Handler());
377             audioRecord.removeOnRoutingChangedListener(
378                     (AudioRecord.OnRoutingChangedListener) listener);
379         }
380 
381         audioRecord.release();
382         if (myLooper != null) {
383             myLooper.quit();
384         }
385     }
386 
test_audioRecord_preferredDevice()387     public void test_audioRecord_preferredDevice() {
388         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
389             // Can't do it so skip this test
390             return;
391         }
392 
393         AudioRecord audioRecord = allocAudioRecord();
394         assertNotNull(audioRecord);
395 
396         // None selected (new AudioRecord), so check for default
397         assertNull(audioRecord.getPreferredDevice());
398 
399         // resets to default
400         assertTrue(audioRecord.setPreferredDevice(null));
401 
402         // test each device
403         AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
404         for (int index = 0; index < deviceList.length; index++) {
405             assertTrue(audioRecord.setPreferredDevice(deviceList[index]));
406             assertTrue(audioRecord.getPreferredDevice() == deviceList[index]);
407         }
408 
409         // Check defaults again
410         assertTrue(audioRecord.setPreferredDevice(null));
411         assertNull(audioRecord.getPreferredDevice());
412 
413         audioRecord.release();
414     }
415 
416     private class AudioTrackFiller implements Runnable {
417         AudioTrack mAudioTrack;
418         int mBufferSize;
419 
420         boolean mPlaying;
421 
422         short[] mAudioData;
423 
AudioTrackFiller(AudioTrack audioTrack, int bufferSize)424         public AudioTrackFiller(AudioTrack audioTrack, int bufferSize) {
425             mAudioTrack = audioTrack;
426             mBufferSize = bufferSize;
427             mPlaying = false;
428 
429             // setup audio data (silence will suffice)
430             mAudioData = new short[mBufferSize];
431             for (int index = 0; index < mBufferSize; index++) {
432                 mAudioData[index] = 0;
433             }
434         }
435 
start()436         public void start() { mPlaying = true; }
stop()437         public void stop() { mPlaying = false; }
438 
439         @Override
run()440         public void run() {
441             while (mAudioTrack != null && mPlaying) {
442                 mAudioTrack.write(mAudioData, 0, mBufferSize);
443             }
444         }
445     }
446 
test_audioTrack_getRoutedDevice()447     public void test_audioTrack_getRoutedDevice() throws Exception {
448         if (!DeviceUtils.hasOutputDevice(mAudioManager)) {
449             Log.i(TAG, "No output devices. Test skipped");
450             return; // nothing to test here
451         }
452 
453         int bufferSize =
454                 AudioTrack.getMinBufferSize(
455                     41000,
456                     AudioFormat.CHANNEL_OUT_STEREO,
457                     AudioFormat.ENCODING_PCM_16BIT);
458         AudioTrack audioTrack =
459             new AudioTrack(
460                 AudioManager.STREAM_MUSIC,
461                 41000,
462                 AudioFormat.CHANNEL_OUT_STEREO,
463                 AudioFormat.ENCODING_PCM_16BIT,
464                 bufferSize,
465                 AudioTrack.MODE_STREAM);
466 
467         AudioTrackFiller filler = new AudioTrackFiller(audioTrack, bufferSize);
468         filler.start();
469 
470         audioTrack.play();
471 
472         Thread fillerThread = new Thread(filler);
473         fillerThread.start();
474 
475         assertHasNonNullRoutedDevice(audioTrack);
476 
477         filler.stop();
478         audioTrack.stop();
479         audioTrack.release();
480     }
481 
assertHasNonNullRoutedDevice(AudioRouting router)482     private void assertHasNonNullRoutedDevice(AudioRouting router) throws Exception {
483         AudioDeviceInfo routedDevice = null;
484         // Give a chance for playback or recording to start so routing can be established
485         final long timeouts[] = { 100, 200, 300, 500, 1000};
486         int attempt = 0;
487         long totalWait = 0;
488         do {
489             totalWait += timeouts[attempt];
490             try { Thread.sleep(timeouts[attempt++]); } catch (InterruptedException ex) {}
491             routedDevice = router.getRoutedDevice();
492             if (routedDevice == null && (attempt > 2 || totalWait >= 1000)) {
493                 Log.w(TAG, "Routing still not reported after " + totalWait + "ms");
494             }
495         } while (routedDevice == null && attempt < timeouts.length);
496         assertNotNull(routedDevice); // we probably can't say anything more than this
497     }
498 
499     private class AudioRecordPuller implements Runnable {
500         AudioRecord mAudioRecord;
501         int mBufferSize;
502 
503         boolean mRecording;
504 
505         short[] mAudioData;
506 
AudioRecordPuller(AudioRecord audioRecord, int bufferSize)507         public AudioRecordPuller(AudioRecord audioRecord, int bufferSize) {
508             mAudioRecord = audioRecord;
509             mBufferSize = bufferSize;
510             mRecording = false;
511         }
512 
start()513         public void start() { mRecording = true; }
stop()514         public void stop() { mRecording = false; }
515 
516         @Override
run()517         public void run() {
518             while (mAudioRecord != null && mRecording) {
519                 mAudioRecord.read(mAudioData, 0, mBufferSize);
520            }
521         }
522     }
523 
test_audioRecord_getRoutedDevice()524     public void test_audioRecord_getRoutedDevice() throws Exception {
525         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
526             return;
527         }
528 
529         if (!DeviceUtils.hasInputDevice(mAudioManager)) {
530             Log.i(TAG, "No input devices. Test skipped");
531             return; // nothing to test here
532         }
533 
534         int bufferSize =
535                 AudioRecord.getMinBufferSize(
536                     41000,
537                     AudioFormat.CHANNEL_OUT_DEFAULT,
538                     AudioFormat.ENCODING_PCM_16BIT);
539         AudioRecord audioRecord =
540             new AudioRecord(
541                 MediaRecorder.AudioSource.DEFAULT,
542                 41000, AudioFormat.CHANNEL_OUT_DEFAULT,
543                 AudioFormat.ENCODING_PCM_16BIT,
544                 bufferSize);
545 
546         AudioRecordPuller puller = new AudioRecordPuller(audioRecord, bufferSize);
547         puller.start();
548 
549         audioRecord.startRecording();
550 
551         Thread pullerThread = new Thread(puller);
552         pullerThread.start();
553 
554         assertHasNonNullRoutedDevice(audioRecord);
555 
556         puller.stop();
557         audioRecord.stop();
558         audioRecord.release();
559     }
560 
561     static class AudioRoutingListener implements AudioRouting.OnRoutingChangedListener
562     {
563         private boolean mCalled;
564         private boolean mCallExpected;
565         private CountDownLatch mCountDownLatch;
566 
AudioRoutingListener()567         AudioRoutingListener() {
568             reset();
569         }
570 
onRoutingChanged(AudioRouting audioRouting)571         public void onRoutingChanged(AudioRouting audioRouting) {
572             mCalled = true;
573             mCountDownLatch.countDown();
574         }
575 
await(long timeoutMs)576         void await(long timeoutMs) {
577             try {
578                 mCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
579             } catch (InterruptedException e) {
580             }
581         }
582 
setCallExpected(boolean flag)583         void setCallExpected(boolean flag) {
584             mCallExpected = flag;
585         }
586 
isCallExpected()587         boolean isCallExpected() {
588             return mCallExpected;
589         }
590 
isRoutingListenerCalled()591         boolean isRoutingListenerCalled() {
592             return mCalled;
593         }
594 
reset()595         void reset() {
596             mCountDownLatch = new CountDownLatch(1);
597             mCalled = false;
598             mCallExpected = true;
599         }
600     }
601 
allocMediaPlayer()602     private MediaPlayer allocMediaPlayer() {
603         return allocMediaPlayer(null, true);
604     }
605 
allocMediaPlayer(AudioDeviceInfo device, boolean start)606     private MediaPlayer allocMediaPlayer(AudioDeviceInfo device, boolean start) {
607         final int resid = R.raw.testmp3_2;
608         MediaPlayer mediaPlayer = MediaPlayer.create(mContext, resid);
609         mediaPlayer.setAudioAttributes(
610                 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build());
611         if (device != null) {
612             mediaPlayer.setPreferredDevice(device);
613         }
614         if (start) {
615             mediaPlayer.start();
616         }
617         return mediaPlayer;
618     }
619 
test_mediaPlayer_preferredDevice()620     public void test_mediaPlayer_preferredDevice() {
621         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
622             // Can't do it so skip this test
623             return;
624         }
625 
626         MediaPlayer mediaPlayer = allocMediaPlayer();
627         assertTrue(mediaPlayer.isPlaying());
628 
629         // None selected (new MediaPlayer), so check for default
630         assertNull(mediaPlayer.getPreferredDevice());
631 
632         // resets to default
633         mediaPlayer.pause();
634         //Wait for state to change before setPreferDevice
635         SystemClock.sleep(200);
636         assertTrue(mediaPlayer.setPreferredDevice(null));
637         mediaPlayer.start();
638         assertTrue(mediaPlayer.isPlaying());
639 
640         // test each device
641         AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
642         for (int index = 0; index < deviceList.length; index++) {
643             if (deviceList[index].getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
644                 // Device with type as TYPE_TELEPHONY requires a privileged permission.
645                 continue;
646             }
647             mediaPlayer.pause();
648             //Wait for state to change before setPreferDevice
649             SystemClock.sleep(200);
650             assertTrue(mediaPlayer.setPreferredDevice(deviceList[index]));
651             mediaPlayer.start();
652             assertTrue(mediaPlayer.isPlaying());
653             assertTrue(mediaPlayer.getPreferredDevice() == deviceList[index]);
654         }
655 
656         // Check defaults again
657         mediaPlayer.pause();
658         //Wait for state to change before setPreferDevice
659         SystemClock.sleep(200);
660         assertTrue(mediaPlayer.setPreferredDevice(null));
661         mediaPlayer.start();
662         assertTrue(mediaPlayer.isPlaying());
663         assertNull(mediaPlayer.getPreferredDevice());
664 
665         mediaPlayer.stop();
666         mediaPlayer.release();
667     }
668 
test_mediaPlayer_getRoutedDevice()669     public void test_mediaPlayer_getRoutedDevice() throws Exception {
670         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
671             // Can't do it so skip this test
672             return;
673         }
674 
675         MediaPlayer mediaPlayer = allocMediaPlayer();
676         assertTrue(mediaPlayer.isPlaying());
677 
678         assertHasNonNullRoutedDevice(mediaPlayer);
679 
680         mediaPlayer.stop();
681         mediaPlayer.release();
682     }
683 
test_MediaPlayer_RoutingListener()684     public void test_MediaPlayer_RoutingListener() {
685         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
686             // Can't do it so skip this test
687             return;
688         }
689 
690         MediaPlayer mediaPlayer = allocMediaPlayer();
691 
692         // null listener
693         mediaPlayer.addOnRoutingChangedListener(null, null);
694 
695         AudioRoutingListener listener = new AudioRoutingListener();
696         AudioRoutingListener someOtherListener = new AudioRoutingListener();
697 
698         // add a listener
699         mediaPlayer.addOnRoutingChangedListener(listener, null);
700 
701         // remove listeners
702         // remove a listener we didn't add
703         mediaPlayer.removeOnRoutingChangedListener(someOtherListener);
704         // remove a valid listener
705         mediaPlayer.removeOnRoutingChangedListener(listener);
706 
707         Looper myLooper = prepareIfNeededLooper();
708 
709         mediaPlayer.addOnRoutingChangedListener(listener, new Handler());
710         mediaPlayer.removeOnRoutingChangedListener(listener);
711 
712         mediaPlayer.stop();
713         mediaPlayer.release();
714         if (myLooper != null) {
715             myLooper.quit();
716         }
717     }
718 
test_MediaPlayer_RoutingChangedCallback()719     public void test_MediaPlayer_RoutingChangedCallback() throws Exception {
720         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
721             // Can't do it so skip this test
722             return;
723         }
724 
725         AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
726         if (devices.length < 2) {
727             // In this case, we cannot switch output device, that may cause the test fail.
728             return;
729         }
730 
731         AudioRoutingListener listener = new AudioRoutingListener();
732         MediaPlayer mediaPlayer = allocMediaPlayer(null, false);
733         mediaPlayer.addOnRoutingChangedListener(listener, null);
734         mediaPlayer.start();
735         try {
736             // Wait a second so that the player
737             Thread.sleep(WAIT_PLAYBACK_START_TIME_MS);
738         } catch (Exception e) {
739         }
740 
741         AudioDeviceInfo routedDevice = mediaPlayer.getRoutedDevice();
742         assertTrue("Routed device should not be null", routedDevice != null);
743 
744         // Reset the routing listener as the listener is called to notify the routed device
745         // when the playback starts.
746         listener.await(WAIT_ROUTING_CHANGE_TIME_MS);
747         assertTrue("Routing changed callback has not been called when starting playback",
748                 listener.isRoutingListenerCalled());
749         listener.reset();
750 
751         listener.setCallExpected(false);
752         for (AudioDeviceInfo device : devices) {
753             if (routedDevice.getId() != device.getId() &&
754                     device.getType() != AudioDeviceInfo.TYPE_TELEPHONY) {
755                 mediaPlayer.pause();
756                 //Wait for state to change before setPreferDevice
757                 SystemClock.sleep(200);
758                 mediaPlayer.setPreferredDevice(device);
759                 mediaPlayer.start();
760                 assertTrue(mediaPlayer.isPlaying());
761                 listener.setCallExpected(true);
762                 listener.await(WAIT_ROUTING_CHANGE_TIME_MS);
763                 break;
764             }
765         }
766 
767         mediaPlayer.removeOnRoutingChangedListener(listener);
768         mediaPlayer.stop();
769         mediaPlayer.release();
770 
771         if (listener.isCallExpected()) {
772             assertTrue("Routing changed callback has not been called",
773                     listener.isRoutingListenerCalled());
774         }
775     }
776 
test_mediaPlayer_incallMusicRoutingPermissions()777     public void test_mediaPlayer_incallMusicRoutingPermissions() {
778         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
779             // Can't do it so skip this test
780             return;
781         }
782 
783         // only apps with MODIFY_PHONE_STATE permission can route playback
784         // to the uplink stream during a phone call, so this test makes sure that
785         // audio is re-routed to default device when the permission is missing
786 
787         AudioDeviceInfo telephonyDevice = getTelephonyDeviceAndSetInCommunicationMode();
788         if (telephonyDevice == null) {
789             // Can't do it so skip this test
790             return;
791         }
792 
793         MediaPlayer mediaPlayer = null;
794 
795         try {
796             mediaPlayer = allocMediaPlayer(telephonyDevice, false);
797             assertEquals(AudioDeviceInfo.TYPE_TELEPHONY, mediaPlayer.getPreferredDevice().getType());
798             mediaPlayer.start();
799             // Sleep for 1s to ensure the underlying AudioTrack is created and started
800             SystemClock.sleep(1000);
801             telephonyDevice = mediaPlayer.getRoutedDevice();
802             // 3 behaviors are accepted when permission to play to telephony device is rejected:
803             // - indicate a null routed device
804             // - fallback to another device for playback
805             // - stop playback in error.
806             assertTrue(telephonyDevice == null
807                     || telephonyDevice.getType() != AudioDeviceInfo.TYPE_TELEPHONY
808                     || !mediaPlayer.isPlaying());
809         } finally {
810             if (mediaPlayer != null) {
811                 mediaPlayer.stop();
812                 mediaPlayer.release();
813             }
814             mAudioManager.setMode(AudioManager.MODE_NORMAL);
815         }
816     }
817 
allocMediaRecorder()818     private MediaRecorder allocMediaRecorder() throws Exception {
819         final String outputPath = new File(mContext.getExternalFilesDir(null),
820             "record.out").getAbsolutePath();
821         mOutFile = new File(outputPath);
822         MediaRecorder mediaRecorder = new MediaRecorder();
823         mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
824         assertEquals(0, mediaRecorder.getMaxAmplitude());
825         mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
826         mediaRecorder.setOutputFile(outputPath);
827         mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
828         mediaRecorder.setAudioChannels(AudioFormat.CHANNEL_OUT_DEFAULT);
829         mediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ);
830         mediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE_IN_BPS);
831         mediaRecorder.setMaxFileSize(MAX_FILE_SIZE_BYTE);
832         mediaRecorder.prepare();
833         mediaRecorder.start();
834         // Sleep a while to ensure the underlying AudioRecord is initialized.
835         Thread.sleep(1000);
836         return mediaRecorder;
837     }
838 
test_mediaRecorder_preferredDevice()839     public void test_mediaRecorder_preferredDevice() throws Exception {
840         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
841                 || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
842             MediaUtils.skipTest("no audio codecs or microphone");
843             return;
844         }
845 
846         MediaRecorder mediaRecorder = allocMediaRecorder();
847 
848         // None selected (new MediaPlayer), so check for default
849         assertNull(mediaRecorder.getPreferredDevice());
850 
851         // resets to default
852         assertTrue(mediaRecorder.setPreferredDevice(null));
853 
854         // test each device
855         AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
856         for (int index = 0; index < deviceList.length; index++) {
857             if (!AVAILABLE_INPUT_DEVICES_TYPE.contains(deviceList[index].getType())) {
858                 // Only try to set devices whose type is contained in predefined set as preferred
859                 // device in case of permission denied when switching input device.
860                 continue;
861             }
862             assertTrue(mediaRecorder.setPreferredDevice(deviceList[index]));
863             assertTrue(mediaRecorder.getPreferredDevice() == deviceList[index]);
864         }
865 
866         // Check defaults again
867         assertTrue(mediaRecorder.setPreferredDevice(null));
868         assertNull(mediaRecorder.getPreferredDevice());
869         Thread.sleep(RECORD_TIME_MS);
870 
871         mediaRecorder.stop();
872         mediaRecorder.release();
873     }
874 
test_mediaRecorder_getRoutedDeviceId()875     public void test_mediaRecorder_getRoutedDeviceId() throws Exception {
876         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
877             || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
878             MediaUtils.skipTest("no audio codecs or microphone");
879             return;
880         }
881 
882         MediaRecorder mediaRecorder = allocMediaRecorder();
883 
884         AudioDeviceInfo routedDevice = mediaRecorder.getRoutedDevice();
885         assertNotNull(routedDevice); // we probably can't say anything more than this
886         Thread.sleep(RECORD_TIME_MS);
887 
888         mediaRecorder.stop();
889         mediaRecorder.release();
890     }
891 
test_mediaRecorder_RoutingListener()892     public void test_mediaRecorder_RoutingListener() throws Exception {
893         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
894             || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
895             MediaUtils.skipTest("no audio codecs or microphone");
896             return;
897         }
898 
899         MediaRecorder mediaRecorder = allocMediaRecorder();
900 
901         // null listener
902         mediaRecorder.addOnRoutingChangedListener(null, null);
903 
904         AudioRoutingListener listener = new AudioRoutingListener();
905         AudioRoutingListener someOtherListener = new AudioRoutingListener();
906 
907         // add a listener
908         mediaRecorder.addOnRoutingChangedListener(listener, null);
909 
910         // remove listeners we didn't add
911         mediaRecorder.removeOnRoutingChangedListener(someOtherListener);
912         // remove a valid listener
913         mediaRecorder.removeOnRoutingChangedListener(listener);
914 
915         Looper myLooper = prepareIfNeededLooper();
916         mediaRecorder.addOnRoutingChangedListener(listener, new Handler());
917         mediaRecorder.removeOnRoutingChangedListener(listener);
918 
919         Thread.sleep(RECORD_TIME_MS);
920 
921         mediaRecorder.stop();
922         mediaRecorder.release();
923         if (myLooper != null) {
924             myLooper.quit();
925         }
926     }
927 }
928