blob: e5f9385094c6efb1a5a93d060dbf13083ac6139a [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media.cts;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Parcel;
import android.util.Log;
import android.media.AudioPlaybackConfiguration;
import com.android.compatibility.common.util.CtsAndroidTestCase;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class AudioPlaybackConfigurationTest extends CtsAndroidTestCase {
private final static String TAG = "AudioPlaybackConfigurationTest";
private final static int TEST_TIMING_TOLERANCE_MS = 50;
private final static int TEST_TIMEOUT_SOUNDPOOL_LOAD_MS = 3000;
// not declared inside test so it can be released in case of failure
private MediaPlayer mMp;
private SoundPool mSp;
@Override
protected void setUp() throws Exception {
super.setUp();
}
@Override
protected void tearDown() throws Exception {
if (mMp != null) {
mMp.stop();
mMp.release();
mMp = null;
}
if (mSp != null) {
mSp.release();
mSp = null;
}
}
private final static int TEST_USAGE = AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED;
private final static int TEST_CONTENT = AudioAttributes.CONTENT_TYPE_SPEECH;
// test marshalling/unmarshalling of an AudioPlaybackConfiguration instance. Since we can't
// create an AudioPlaybackConfiguration directly, we first need to play something to get one.
public void testParcelableWriteToParcel() throws Exception {
if (!isValidPlatform("testParcelableWriteToParcel")) return;
// create a player, make it play so we can get an AudioPlaybackConfiguration instance
AudioManager am = new AudioManager(getContext());
assertNotNull("Could not create AudioManager", am);
final AudioAttributes aa = (new AudioAttributes.Builder())
.setUsage(TEST_USAGE)
.setContentType(TEST_CONTENT)
.build();
mMp = MediaPlayer.create(getContext(), R.raw.sine1khzs40dblong,
aa, am.generateAudioSessionId());
mMp.start();
Thread.sleep(2*TEST_TIMING_TOLERANCE_MS);// waiting for playback to start
List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
mMp.stop();
assertTrue("No playback reported", configs.size() > 0);
AudioPlaybackConfiguration configToMarshall = null;
for (AudioPlaybackConfiguration config : configs) {
if (config.getAudioAttributes().equals(aa)) {
configToMarshall = config;
break;
}
}
assertNotNull("Configuration not found during playback", configToMarshall);
assertEquals(0, configToMarshall.describeContents());
final Parcel srcParcel = Parcel.obtain();
final Parcel dstParcel = Parcel.obtain();
final byte[] mbytes;
configToMarshall.writeToParcel(srcParcel, 0 /*no public flags for marshalling*/);
mbytes = srcParcel.marshall();
dstParcel.unmarshall(mbytes, 0, mbytes.length);
dstParcel.setDataPosition(0);
final AudioPlaybackConfiguration restoredConfig =
AudioPlaybackConfiguration.CREATOR.createFromParcel(dstParcel);
assertEquals("Marshalled/restored AudioAttributes don't match",
configToMarshall.getAudioAttributes(), restoredConfig.getAudioAttributes());
}
public void testGetterMediaPlayer() throws Exception {
if (!isValidPlatform("testGetterMediaPlayer")) return;
AudioManager am = new AudioManager(getContext());
assertNotNull("Could not create AudioManager", am);
final AudioAttributes aa = (new AudioAttributes.Builder())
.setUsage(TEST_USAGE)
.setContentType(TEST_CONTENT)
.build();
List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
final int nbActivePlayersBeforeStart = configs.size();
mMp = MediaPlayer.create(getContext(), R.raw.sine1khzs40dblong,
aa, am.generateAudioSessionId());
configs = am.getActivePlaybackConfigurations();
assertEquals("inactive MediaPlayer, number of configs shouldn't have changed",
nbActivePlayersBeforeStart /*expected*/, configs.size());
mMp.start();
Thread.sleep(2*TEST_TIMING_TOLERANCE_MS);// waiting for playback to start
configs = am.getActivePlaybackConfigurations();
assertEquals("active MediaPlayer, number of configs should have increased",
nbActivePlayersBeforeStart + 1 /*expected*/,
configs.size());
assertTrue("Active player, attributes not found", hasAttr(configs, aa));
// verify "privileged" fields aren't available through reflection
final AudioPlaybackConfiguration config = configs.get(0);
final Class<?> confClass = config.getClass();
final Method getClientUidMethod = confClass.getDeclaredMethod("getClientUid");
final Method getClientPidMethod = confClass.getDeclaredMethod("getClientPid");
final Method getPlayerTypeMethod = confClass.getDeclaredMethod("getPlayerType");
try {
Integer uid = (Integer) getClientUidMethod.invoke(config, null);
assertEquals("uid isn't protected", -1 /*expected*/, uid.intValue());
Integer pid = (Integer) getClientPidMethod.invoke(config, null);
assertEquals("pid isn't protected", -1 /*expected*/, pid.intValue());
Integer type = (Integer) getPlayerTypeMethod.invoke(config, null);
assertEquals("player type isn't protected", -1 /*expected*/, type.intValue());
} catch (Exception e) {
fail("Exception thrown during reflection on config privileged fields"+ e);
}
}
public void testCallbackMediaPlayer() throws Exception {
if (!isValidPlatform("testCallbackMediaPlayer")) return;
doTestCallbackMediaPlayer(false /* no custom Handler for callback */);
}
public void testCallbackMediaPlayerHandler() throws Exception {
if (!isValidPlatform("testCallbackMediaPlayerHandler")) return;
doTestCallbackMediaPlayer(true /* use custom Handler for callback */);
}
private void doTestCallbackMediaPlayer(boolean useHandlerInCallback) throws Exception {
final Handler h;
if (useHandlerInCallback) {
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
h = new Handler(handlerThread.getLooper());
} else {
h = null;
}
try {
AudioManager am = new AudioManager(getContext());
assertNotNull("Could not create AudioManager", am);
final AudioAttributes aa = (new AudioAttributes.Builder())
.setUsage(TEST_USAGE)
.setContentType(TEST_CONTENT)
.build();
mMp = MediaPlayer.create(getContext(), R.raw.sine1khzs40dblong,
aa, am.generateAudioSessionId());
MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
am.registerAudioPlaybackCallback(callback, h /*handler*/);
// query how many active players before starting the MediaPlayer
List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
final int nbActivePlayersBeforeStart = configs.size();
mMp.start();
Thread.sleep(TEST_TIMING_TOLERANCE_MS);
assertEquals("onPlaybackConfigChanged call count not expected",
1/*expected*/, callback.getCbInvocationNumber()); //only one start call
assertEquals("number of active players not expected",
// one more player active
nbActivePlayersBeforeStart + 1/*expected*/, callback.getNbConfigs());
assertTrue("Active player, attributes not found", hasAttr(callback.getConfigs(), aa));
// stopping recording: callback is called with no match
callback.reset();
mMp.pause();
Thread.sleep(TEST_TIMING_TOLERANCE_MS);
assertEquals("onPlaybackConfigChanged call count not expected after pause",
1/*expected*/, callback.getCbInvocationNumber());//only 1 pause call since reset
assertEquals("number of active players not expected after pause",
nbActivePlayersBeforeStart/*expected*/, callback.getNbConfigs());
// unregister callback and start recording again
am.unregisterAudioPlaybackCallback(callback);
Thread.sleep(TEST_TIMING_TOLERANCE_MS);
callback.reset();
mMp.start();
Thread.sleep(TEST_TIMING_TOLERANCE_MS);
assertEquals("onPlaybackConfigChanged call count not expected after unregister",
0/*expected*/, callback.getCbInvocationNumber()); //callback is unregistered
// just call the callback once directly so it's marked as tested
final AudioManager.AudioPlaybackCallback apc =
(AudioManager.AudioPlaybackCallback) callback;
apc.onPlaybackConfigChanged(new ArrayList<AudioPlaybackConfiguration>());
} finally {
if (h != null) {
h.getLooper().quit();
}
}
}
public void testGetterSoundPool() throws Exception {
if (!isValidPlatform("testSoundPool")) return;
AudioManager am = new AudioManager(getContext());
assertNotNull("Could not create AudioManager", am);
MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
am.registerAudioPlaybackCallback(callback, null /*handler*/);
// query how many active players before starting the SoundPool
List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
int nbActivePlayersBeforeStart = 0;
for (AudioPlaybackConfiguration apc : configs) {
if (apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
nbActivePlayersBeforeStart++;
}
}
final AudioAttributes aa = (new AudioAttributes.Builder())
.setUsage(TEST_USAGE)
.setContentType(TEST_CONTENT)
.build();
mSp = new SoundPool.Builder()
.setAudioAttributes(aa)
.setMaxStreams(1)
.build();
final Object loadLock = new Object();
final SoundPool zepool = mSp;
// load a sound and play it once load completion is reported
mSp.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
@Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
assertEquals("Receiving load completion for wrong SoundPool", zepool, mSp);
assertEquals("Load completion error", 0 /*success expected*/, status);
synchronized (loadLock) {
loadLock.notify();
}
}
});
final int loadId = mSp.load(getContext(), R.raw.sine1320hz5sec, 1/*priority*/);
synchronized (loadLock) {
loadLock.wait(TEST_TIMEOUT_SOUNDPOOL_LOAD_MS);
}
int res = mSp.play(loadId, 1.0f /*leftVolume*/, 1.0f /*rightVolume*/, 1 /*priority*/,
0 /*loop*/, 1.0f/*rate*/);
// FIXME SoundPool activity is not reported yet, but exercise creation/release with
// an AudioPlaybackCallback registered
assertTrue("Error playing sound through SoundPool", res > 0);
Thread.sleep(TEST_TIMING_TOLERANCE_MS);
mSp.autoPause();
Thread.sleep(TEST_TIMING_TOLERANCE_MS);
// query how many active players after pausing
configs = am.getActivePlaybackConfigurations();
int nbActivePlayersAfterPause = 0;
for (AudioPlaybackConfiguration apc : configs) {
if (apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
nbActivePlayersAfterPause++;
}
}
assertEquals("Number of active players changed after pausing SoundPool",
nbActivePlayersBeforeStart, nbActivePlayersAfterPause);
}
private static class MyAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback {
private int mCalled = 0;
private int mNbConfigs = 0;
private List<AudioPlaybackConfiguration> mConfigs;
private final Object mCbLock = new Object();
void reset() {
synchronized (mCbLock) {
mCalled = 0;
mNbConfigs = 0;
mConfigs.clear();
}
}
int getCbInvocationNumber() {
synchronized (mCbLock) {
return mCalled;
}
}
int getNbConfigs() {
synchronized (mCbLock) {
return mNbConfigs;
}
}
List<AudioPlaybackConfiguration> getConfigs() {
synchronized (mCbLock) {
return mConfigs;
}
}
MyAudioPlaybackCallback() {
}
@Override
public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
synchronized (mCbLock) {
mCalled++;
mNbConfigs = configs.size();
mConfigs = configs;
}
}
}
private static boolean hasAttr(List<AudioPlaybackConfiguration> configs, AudioAttributes aa) {
Iterator<AudioPlaybackConfiguration> it = configs.iterator();
while (it.hasNext()) {
final AudioPlaybackConfiguration apc = it.next();
if (apc.getAudioAttributes().getContentType() == aa.getContentType()
&& apc.getAudioAttributes().getUsage() == aa.getUsage()) {
return true;
}
}
return false;
}
private boolean isValidPlatform(String testName) {
if (!getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL, skipping test " + testName);
return false;
}
return true;
}
}