Merge "AudioManager: Add functional tests for productstrategy/volumegroup" into rvc-dev am: 5fb2147af2

Change-Id: I70963769ee5d6184a7cc34bcb654d76dbd5c15c8
This commit is contained in:
Eric Laurent 2020-04-14 22:56:50 +00:00 committed by Automerger Merge Worker
commit 48b9be33e1
12 changed files with 1220 additions and 0 deletions

View File

@ -0,0 +1,17 @@
android_test {
name: "audiopolicytest",
srcs: ["**/*.java"],
libs: [
"android.test.runner",
"android.test.base",
],
static_libs: [
"mockito-target-minus-junit4",
"androidx.test.rules",
"android-ex-camera2",
"testng",
],
platform_apis: true,
certificate: "platform",
resource_dirs: ["res"],
}

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.audiopolicytest">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
<uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
<application>
<uses-library android:name="android.test.runner" />
<activity android:label="@string/app_name" android:name="AudioPolicyTest"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
<!--instrumentation android:name=".AudioPolicyTestRunner"
android:targetPackage="com.android.audiopolicytest"
android:label="AudioManager policy oriented integration tests InstrumentationRunner">
</instrumentation-->
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.audiopolicytest"
android:label="AudioManager policy oriented integration tests InstrumentationRunner">
</instrumentation>
</manifest>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 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.
-->
<configuration description="Runs Media Framework Tests">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
<option name="test-file-name" value="audiopolicytest.apk" />
</target_preparer>
<option name="test-tag" value="AudioPolicyTest" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.audiopolicytest" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
</test>
</configuration>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
</LinearLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- name of the app [CHAR LIMIT=25]-->
<string name="app_name">Audio Policy APIs Tests</string>
</resources>

View File

@ -0,0 +1,328 @@
/*
* Copyright (C) 2020 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 com.android.audiopolicytest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.testng.Assert.assertThrows;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
import android.util.Log;
import com.google.common.primitives.Ints;
import java.util.List;
public class AudioManagerTest extends AudioVolumesTestBase {
private static final String TAG = "AudioManagerTest";
//-----------------------------------------------------------------
// Test getAudioProductStrategies and validate strategies
//-----------------------------------------------------------------
public void testGetAndValidateProductStrategies() throws Exception {
List<AudioProductStrategy> audioProductStrategies =
mAudioManager.getAudioProductStrategies();
assertTrue(audioProductStrategies.size() > 0);
List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
assertTrue(audioVolumeGroups.size() > 0);
// Validate Audio Product Strategies
for (final AudioProductStrategy audioProductStrategy : audioProductStrategies) {
AudioAttributes attributes = audioProductStrategy.getAudioAttributes();
int strategyStreamType =
audioProductStrategy.getLegacyStreamTypeForAudioAttributes(attributes);
assertTrue("Strategy shall support the attributes retrieved from its getter API",
audioProductStrategy.supportsAudioAttributes(attributes));
int volumeGroupId =
audioProductStrategy.getVolumeGroupIdForAudioAttributes(attributes);
// A strategy must be associated to a volume group
assertNotEquals("strategy not assigned to any volume group",
volumeGroupId, AudioVolumeGroup.DEFAULT_VOLUME_GROUP);
// Valid Group ?
AudioVolumeGroup audioVolumeGroup = null;
for (final AudioVolumeGroup avg : audioVolumeGroups) {
if (avg.getId() == volumeGroupId) {
audioVolumeGroup = avg;
break;
}
}
assertNotNull("Volume Group not found", audioVolumeGroup);
// Cross check: the group shall have at least one aa / stream types following the
// considered strategy
boolean strategyAttributesSupported = false;
for (final AudioAttributes aa : audioVolumeGroup.getAudioAttributes()) {
if (audioProductStrategy.supportsAudioAttributes(aa)) {
strategyAttributesSupported = true;
break;
}
}
assertTrue("Volume Group and Strategy mismatching", strategyAttributesSupported);
// Some Product strategy may not have corresponding stream types as they intends
// to address volume setting per attributes to avoid adding new stream type
// and going on deprecating the stream type even for volume
if (strategyStreamType != AudioSystem.STREAM_DEFAULT) {
boolean strategStreamTypeSupported = false;
for (final int vgStreamType : audioVolumeGroup.getLegacyStreamTypes()) {
if (vgStreamType == strategyStreamType) {
strategStreamTypeSupported = true;
break;
}
}
assertTrue("Volume Group and Strategy mismatching", strategStreamTypeSupported);
}
}
}
//-----------------------------------------------------------------
// Test getAudioVolumeGroups and validate volume groups
//-----------------------------------------------------------------
public void testGetAndValidateVolumeGroups() throws Exception {
List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
assertTrue(audioVolumeGroups.size() > 0);
List<AudioProductStrategy> audioProductStrategies =
mAudioManager.getAudioProductStrategies();
assertTrue(audioProductStrategies.size() > 0);
// Validate Audio Volume Groups, check all
for (final AudioVolumeGroup audioVolumeGroup : audioVolumeGroups) {
List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes();
int[] avgStreamTypes = audioVolumeGroup.getLegacyStreamTypes();
// for each volume group attributes, find the matching product strategy and ensure
// it is linked the considered volume group
for (final AudioAttributes aa : avgAttributes) {
if (aa.equals(sDefaultAttributes)) {
// Some volume groups may not have valid attributes, used for internal
// volume management like patch/rerouting
// so bailing out strategy retrieval from attributes
continue;
}
boolean isVolumeGroupAssociatedToStrategy = false;
for (final AudioProductStrategy strategy : audioProductStrategies) {
int groupId = strategy.getVolumeGroupIdForAudioAttributes(aa);
if (groupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
assertEquals("Volume Group ID (" + audioVolumeGroup.toString()
+ "), and Volume group ID associated to Strategy ("
+ strategy.toString() + ") both supporting attributes "
+ aa.toString() + " are mismatching",
audioVolumeGroup.getId(), groupId);
isVolumeGroupAssociatedToStrategy = true;
break;
}
}
assertTrue("Volume Group (" + audioVolumeGroup.toString()
+ ") has no associated strategy for attributes " + aa.toString(),
isVolumeGroupAssociatedToStrategy);
}
// for each volume group stream type, find the matching product strategy and ensure
// it is linked the considered volume group
for (final int avgStreamType : avgStreamTypes) {
if (avgStreamType == AudioSystem.STREAM_DEFAULT) {
// Some Volume Groups may not have corresponding stream types as they
// intends to address volume setting per attributes to avoid adding new
// stream type and going on deprecating the stream type even for volume
// so bailing out strategy retrieval from stream type
continue;
}
boolean isVolumeGroupAssociatedToStrategy = false;
for (final AudioProductStrategy strategy : audioProductStrategies) {
Log.i(TAG, "strategy:" + strategy.toString());
int groupId = strategy.getVolumeGroupIdForLegacyStreamType(avgStreamType);
if (groupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
assertEquals("Volume Group ID (" + audioVolumeGroup.toString()
+ "), and Volume group ID associated to Strategy ("
+ strategy.toString() + ") both supporting stream "
+ AudioSystem.streamToString(avgStreamType) + "("
+ avgStreamType + ") are mismatching",
audioVolumeGroup.getId(), groupId);
isVolumeGroupAssociatedToStrategy = true;
break;
}
}
assertTrue("Volume Group (" + audioVolumeGroup.toString()
+ ") has no associated strategy for stream "
+ AudioSystem.streamToString(avgStreamType) + "(" + avgStreamType + ")",
isVolumeGroupAssociatedToStrategy);
}
}
}
//-----------------------------------------------------------------
// Test Volume per Attributes setter/getters
//-----------------------------------------------------------------
public void testSetGetVolumePerAttributesWithInvalidAttributes() throws Exception {
AudioAttributes nullAttributes = null;
assertThrows(NullPointerException.class,
() -> mAudioManager.getMaxVolumeIndexForAttributes(nullAttributes));
assertThrows(NullPointerException.class,
() -> mAudioManager.getMinVolumeIndexForAttributes(nullAttributes));
assertThrows(NullPointerException.class,
() -> mAudioManager.getVolumeIndexForAttributes(nullAttributes));
assertThrows(NullPointerException.class,
() -> mAudioManager.setVolumeIndexForAttributes(
nullAttributes, 0 /*index*/, 0/*flags*/));
}
public void testSetGetVolumePerAttributes() throws Exception {
for (int usage : AudioAttributes.SDK_USAGES) {
if (usage == AudioAttributes.USAGE_UNKNOWN) {
continue;
}
AudioAttributes aaForUsage = new AudioAttributes.Builder().setUsage(usage).build();
int indexMin = 0;
int indexMax = 0;
int index = 0;
Exception ex = null;
try {
indexMax = mAudioManager.getMaxVolumeIndexForAttributes(aaForUsage);
} catch (Exception e) {
ex = e; // unexpected
}
assertNull("Exception was thrown for valid attributes", ex);
ex = null;
try {
indexMin = mAudioManager.getMinVolumeIndexForAttributes(aaForUsage);
} catch (Exception e) {
ex = e; // unexpected
}
assertNull("Exception was thrown for valid attributes", ex);
ex = null;
try {
index = mAudioManager.getVolumeIndexForAttributes(aaForUsage);
} catch (Exception e) {
ex = e; // unexpected
}
assertNull("Exception was thrown for valid attributes", ex);
ex = null;
try {
mAudioManager.setVolumeIndexForAttributes(aaForUsage, indexMin, 0/*flags*/);
} catch (Exception e) {
ex = e; // unexpected
}
assertNull("Exception was thrown for valid attributes", ex);
index = mAudioManager.getVolumeIndexForAttributes(aaForUsage);
assertEquals(index, indexMin);
mAudioManager.setVolumeIndexForAttributes(aaForUsage, indexMax, 0/*flags*/);
index = mAudioManager.getVolumeIndexForAttributes(aaForUsage);
assertEquals(index, indexMax);
}
}
//-----------------------------------------------------------------
// Test register/unregister VolumeGroupCallback
//-----------------------------------------------------------------
public void testVolumeGroupCallback() throws Exception {
List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
assertTrue(audioVolumeGroups.size() > 0);
AudioVolumeGroupCallbackHelper vgCbReceiver = new AudioVolumeGroupCallbackHelper();
mAudioManager.registerVolumeGroupCallback(mContext.getMainExecutor(), vgCbReceiver);
final List<Integer> publicStreams = Ints.asList(PUBLIC_STREAM_TYPES);
try {
// Validate Audio Volume Groups callback reception
for (final AudioVolumeGroup audioVolumeGroup : audioVolumeGroups) {
int volumeGroupId = audioVolumeGroup.getId();
// Set the receiver to filter only the current group callback
vgCbReceiver.setExpectedVolumeGroup(volumeGroupId);
List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes();
int[] avgStreamTypes = audioVolumeGroup.getLegacyStreamTypes();
int index = 0;
int indexMax = 0;
int indexMin = 0;
// Set the volume per attributes (if valid) and wait the callback
for (final AudioAttributes aa : avgAttributes) {
if (aa.equals(sDefaultAttributes)) {
// Some volume groups may not have valid attributes, used for internal
// volume management like patch/rerouting
// so bailing out strategy retrieval from attributes
continue;
}
index = mAudioManager.getVolumeIndexForAttributes(aa);
indexMax = mAudioManager.getMaxVolumeIndexForAttributes(aa);
indexMin = mAudioManager.getMinVolumeIndexForAttributes(aa);
index = incrementVolumeIndex(index, indexMin, indexMax);
vgCbReceiver.setExpectedVolumeGroup(volumeGroupId);
mAudioManager.setVolumeIndexForAttributes(aa, index, 0/*flags*/);
assertTrue(vgCbReceiver.waitForExpectedVolumeGroupChanged(
AudioVolumeGroupCallbackHelper.ASYNC_TIMEOUT_MS));
int readIndex = mAudioManager.getVolumeIndexForAttributes(aa);
assertEquals(readIndex, index);
}
// Set the volume per stream type (if valid) and wait the callback
for (final int avgStreamType : avgStreamTypes) {
if (avgStreamType == AudioSystem.STREAM_DEFAULT) {
// Some Volume Groups may not have corresponding stream types as they
// intends to address volume setting per attributes to avoid adding new
// stream type and going on deprecating the stream type even for volume
// so bailing out strategy retrieval from stream type
continue;
}
if (!publicStreams.contains(avgStreamType)
|| avgStreamType == AudioManager.STREAM_ACCESSIBILITY) {
// Limit scope of test to public stream that do not require any
// permission (e.g. Changing ACCESSIBILITY is subject to permission).
continue;
}
index = mAudioManager.getStreamVolume(avgStreamType);
indexMax = mAudioManager.getStreamMaxVolume(avgStreamType);
indexMin = mAudioManager.getStreamMinVolumeInt(avgStreamType);
index = incrementVolumeIndex(index, indexMin, indexMax);
vgCbReceiver.setExpectedVolumeGroup(volumeGroupId);
mAudioManager.setStreamVolume(avgStreamType, index, 0/*flags*/);
assertTrue(vgCbReceiver.waitForExpectedVolumeGroupChanged(
AudioVolumeGroupCallbackHelper.ASYNC_TIMEOUT_MS));
int readIndex = mAudioManager.getStreamVolume(avgStreamType);
assertEquals(index, readIndex);
}
}
} finally {
mAudioManager.unregisterVolumeGroupCallback(vgCbReceiver);
}
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2020 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 com.android.audiopolicytest;
import android.app.Activity;
import android.os.Bundle;
public class AudioPolicyTest extends Activity {
public AudioPolicyTest() {
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.audiopolicytest);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}

View File

@ -0,0 +1,213 @@
/*
* Copyright (C) 2020 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 com.android.audiopolicytest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import android.media.AudioAttributes;
import android.media.AudioSystem;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
import android.util.Log;
import java.util.List;
public class AudioProductStrategyTest extends AudioVolumesTestBase {
private static final String TAG = "AudioProductStrategyTest";
//-----------------------------------------------------------------
// Test getAudioProductStrategies and validate strategies
//-----------------------------------------------------------------
public void testGetProductStrategies() throws Exception {
List<AudioProductStrategy> audioProductStrategies =
AudioProductStrategy.getAudioProductStrategies();
assertNotNull(audioProductStrategies);
assertTrue(audioProductStrategies.size() > 0);
for (final AudioProductStrategy aps : audioProductStrategies) {
assertTrue(aps.getId() >= 0);
AudioAttributes aa = aps.getAudioAttributes();
assertNotNull(aa);
// Ensure API consistency
assertTrue(aps.supportsAudioAttributes(aa));
int streamType = aps.getLegacyStreamTypeForAudioAttributes(aa);
if (streamType == AudioSystem.STREAM_DEFAULT) {
// bailing out test for volume group APIs consistency
continue;
}
final int volumeGroupFromStream = aps.getVolumeGroupIdForLegacyStreamType(streamType);
final int volumeGroupFromAttributes = aps.getVolumeGroupIdForAudioAttributes(aa);
assertNotEquals(volumeGroupFromStream, AudioVolumeGroup.DEFAULT_VOLUME_GROUP);
assertEquals(volumeGroupFromStream, volumeGroupFromAttributes);
}
}
//-----------------------------------------------------------------
// Test stream to/from attributes conversion
//-----------------------------------------------------------------
public void testAudioAttributesFromStreamTypes() throws Exception {
List<AudioProductStrategy> audioProductStrategies =
AudioProductStrategy.getAudioProductStrategies();
assertNotNull(audioProductStrategies);
assertTrue(audioProductStrategies.size() > 0);
for (final int streamType : PUBLIC_STREAM_TYPES) {
AudioAttributes aaFromStreamType =
AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType(
streamType);
// No strategy found for this stream type or no attributes defined for the strategy
// hosting this stream type; Bailing out the test, just ensure that any request
// for reciproque API with the unknown attributes would return default stream
// for volume control, aka STREAM_MUSIC.
if (aaFromStreamType.equals(sInvalidAttributes)) {
assertEquals(AudioSystem.STREAM_MUSIC,
AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(
aaFromStreamType));
} else {
// Attributes are valid, i.e. a strategy was found supporting this stream type
// with valid attributes. Ensure reciproque works fine
int streamTypeFromAttributes =
AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(
aaFromStreamType);
assertEquals("stream " + AudioSystem.streamToString(streamType) + "("
+ streamType + ") expected to match attributes "
+ aaFromStreamType.toString() + " got instead stream "
+ AudioSystem.streamToString(streamTypeFromAttributes) + "("
+ streamTypeFromAttributes + ") expected to match attributes ",
streamType, streamTypeFromAttributes);
}
// Now identify the strategy supporting this stream type, ensure uniqueness
boolean strategyFound = false;
for (final AudioProductStrategy aps : audioProductStrategies) {
AudioAttributes aaFromAps =
aps.getAudioAttributesForLegacyStreamType(streamType);
if (aaFromAps == null) {
// not this one...
continue;
}
// Got it!
assertFalse("Unique ProductStrategy shall match for a given stream type",
strategyFound);
strategyFound = true;
// Ensure getters aligned
assertEquals(aaFromStreamType, aaFromAps);
assertTrue(aps.supportsAudioAttributes(aaFromStreamType));
// Ensure reciproque works fine
assertEquals(streamType,
aps.getLegacyStreamTypeForAudioAttributes(aaFromStreamType));
// Ensure consistency of volume group getter API
final int volumeGroupFromStream =
aps.getVolumeGroupIdForLegacyStreamType(streamType);
final int volumeGroupFromAttributes =
aps.getVolumeGroupIdForAudioAttributes(aaFromStreamType);
assertNotEquals(volumeGroupFromStream, AudioVolumeGroup.DEFAULT_VOLUME_GROUP);
assertEquals(volumeGroupFromStream, volumeGroupFromAttributes);
}
if (!strategyFound) {
// No strategy found, ensure volume control is MUSIC
assertEquals(AudioSystem.STREAM_MUSIC,
AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(
aaFromStreamType));
}
}
}
public void testAudioAttributesToStreamTypes() throws Exception {
List<AudioProductStrategy> audioProductStrategies =
AudioProductStrategy.getAudioProductStrategies();
assertNotNull(audioProductStrategies);
assertTrue(audioProductStrategies.size() > 0);
for (int usage : AudioAttributes.SDK_USAGES) {
AudioAttributes aaForUsage = new AudioAttributes.Builder().setUsage(usage).build();
int streamTypeFromUsage =
AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(
aaForUsage);
// Cannot be undefined, always shall fall back on a valid stream type
// to be able to control the volume
assertNotEquals(streamTypeFromUsage, AudioSystem.STREAM_DEFAULT);
Log.w(TAG, "GUSTAVE aaForUsage=" + aaForUsage.toString());
// Now identify the strategy hosting these Audio Attributes and ensure informations
// matches.
// Now identify the strategy supporting this stream type, ensure uniqueness
boolean strategyFound = false;
for (final AudioProductStrategy aps : audioProductStrategies) {
if (!aps.supportsAudioAttributes(aaForUsage)) {
// Not this one
continue;
}
// Got it!
String msg = "Unique ProductStrategy shall match for a given audio attributes "
+ aaForUsage.toString() + " already associated also matches with"
+ aps.toString();
assertFalse(msg, strategyFound);
strategyFound = true;
// It may not return the expected stream type if the strategy does not have
// associated stream type.
// Behavior of member function getLegacyStreamTypeForAudioAttributes is
// different than getLegacyStreamTypeForStrategyWithAudioAttributes since it
// does not fallback on MUSIC stream type for volume operation
int streamTypeFromAps = aps.getLegacyStreamTypeForAudioAttributes(aaForUsage);
if (streamTypeFromAps == AudioSystem.STREAM_DEFAULT) {
// No stream type assigned to this strategy
// Expect static API to return default stream type for volume (aka MUSIC)
assertEquals("Strategy (" + aps.toString() + ") has no associated stream "
+ ", must fallback on MUSIC stream as default",
streamTypeFromUsage, AudioSystem.STREAM_MUSIC);
} else {
assertEquals("Attributes " + aaForUsage.toString() + " associated to stream "
+ AudioSystem.streamToString(streamTypeFromUsage)
+ " are supported by strategy (" + aps.toString() + ") which reports "
+ " these attributes are associated to stream "
+ AudioSystem.streamToString(streamTypeFromAps),
streamTypeFromUsage, streamTypeFromAps);
// Ensure consistency of volume group getter API
int volumeGroupFromStream =
aps.getVolumeGroupIdForLegacyStreamType(streamTypeFromAps);
int volumeGroupFromAttributes =
aps.getVolumeGroupIdForAudioAttributes(aaForUsage);
assertNotEquals(
volumeGroupFromStream, AudioVolumeGroup.DEFAULT_VOLUME_GROUP);
assertEquals(volumeGroupFromStream, volumeGroupFromAttributes);
}
}
if (!strategyFound) {
// No strategy found for the given attributes, the expected stream must be MUSIC
assertEquals(streamTypeFromUsage, AudioSystem.STREAM_MUSIC);
}
}
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (C) 2020 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 com.android.audiopolicytest;
import static org.junit.Assert.assertNotNull;
import android.media.AudioManager;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
final class AudioVolumeGroupCallbackHelper extends AudioManager.VolumeGroupCallback {
private static final String TAG = "AudioVolumeGroupCallbackHelper";
public static final long ASYNC_TIMEOUT_MS = 800;
private int mExpectedVolumeGroupId;
private CountDownLatch mVolumeGroupChanged = null;
void setExpectedVolumeGroup(int group) {
mVolumeGroupChanged = new CountDownLatch(1);
mExpectedVolumeGroupId = group;
}
@Override
public void onAudioVolumeGroupChanged(int group, int flags) {
if (group != mExpectedVolumeGroupId) {
return;
}
if (mVolumeGroupChanged == null) {
Log.wtf(TAG, "Received callback but object not initialized");
return;
}
if (mVolumeGroupChanged.getCount() <= 0) {
Log.i(TAG, "callback for group: " + group + " already received");
return;
}
mVolumeGroupChanged.countDown();
}
public boolean waitForExpectedVolumeGroupChanged(long timeOutMs) {
assertNotNull("Call first setExpectedVolumeGroup before waiting...", mVolumeGroupChanged);
boolean timeoutReached = false;
if (mVolumeGroupChanged.getCount() == 0) {
// done already...
return true;
}
try {
timeoutReached = !mVolumeGroupChanged.await(ASYNC_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) { }
return !timeoutReached;
}
}

View File

@ -0,0 +1,180 @@
/*
* Copyright (C) 2020 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 com.android.audiopolicytest;
import static org.junit.Assert.assertEquals;
import static org.testng.Assert.assertThrows;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.audiopolicy.AudioVolumeGroup;
import android.media.audiopolicy.AudioVolumeGroupChangeHandler;
import java.util.ArrayList;
import java.util.List;
public class AudioVolumeGroupChangeHandlerTest extends AudioVolumesTestBase {
private static final String TAG = "AudioVolumeGroupChangeHandlerTest";
public void testRegisterInvalidCallback() throws Exception {
final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
new AudioVolumeGroupChangeHandler();
audioAudioVolumeGroupChangedHandler.init();
assertThrows(NullPointerException.class, () -> {
AudioManager.VolumeGroupCallback nullCb = null;
audioAudioVolumeGroupChangedHandler.registerListener(nullCb);
});
}
public void testUnregisterInvalidCallback() throws Exception {
final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
new AudioVolumeGroupChangeHandler();
audioAudioVolumeGroupChangedHandler.init();
final AudioVolumeGroupCallbackHelper cb = new AudioVolumeGroupCallbackHelper();
audioAudioVolumeGroupChangedHandler.registerListener(cb);
assertThrows(NullPointerException.class, () -> {
AudioManager.VolumeGroupCallback nullCb = null;
audioAudioVolumeGroupChangedHandler.unregisterListener(nullCb);
});
audioAudioVolumeGroupChangedHandler.unregisterListener(cb);
}
public void testRegisterUnregisterCallback() throws Exception {
final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
new AudioVolumeGroupChangeHandler();
audioAudioVolumeGroupChangedHandler.init();
final AudioVolumeGroupCallbackHelper validCb = new AudioVolumeGroupCallbackHelper();
// Should not assert, otherwise test will fail
audioAudioVolumeGroupChangedHandler.registerListener(validCb);
// Should not assert, otherwise test will fail
audioAudioVolumeGroupChangedHandler.unregisterListener(validCb);
}
public void testCallbackReceived() throws Exception {
final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
new AudioVolumeGroupChangeHandler();
audioAudioVolumeGroupChangedHandler.init();
final AudioVolumeGroupCallbackHelper validCb = new AudioVolumeGroupCallbackHelper();
audioAudioVolumeGroupChangedHandler.registerListener(validCb);
List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
assertTrue(audioVolumeGroups.size() > 0);
try {
for (final AudioVolumeGroup audioVolumeGroup : audioVolumeGroups) {
int volumeGroupId = audioVolumeGroup.getId();
List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes();
// Set the volume per attributes (if valid) and wait the callback
if (avgAttributes.size() == 0 || avgAttributes.get(0).equals(sDefaultAttributes)) {
// Some volume groups may not have valid attributes, used for internal
// volume management like patch/rerouting
// so bailing out strategy retrieval from attributes
continue;
}
final AudioAttributes aa = avgAttributes.get(0);
int index = mAudioManager.getVolumeIndexForAttributes(aa);
int indexMax = mAudioManager.getMaxVolumeIndexForAttributes(aa);
int indexMin = mAudioManager.getMinVolumeIndexForAttributes(aa);
final int indexForAa = incrementVolumeIndex(index, indexMin, indexMax);
// Set the receiver to filter only the current group callback
validCb.setExpectedVolumeGroup(volumeGroupId);
mAudioManager.setVolumeIndexForAttributes(aa, indexForAa, 0/*flags*/);
assertTrue(validCb.waitForExpectedVolumeGroupChanged(
AudioVolumeGroupCallbackHelper.ASYNC_TIMEOUT_MS));
final int readIndex = mAudioManager.getVolumeIndexForAttributes(aa);
assertEquals(readIndex, indexForAa);
}
} finally {
audioAudioVolumeGroupChangedHandler.unregisterListener(validCb);
}
}
public void testMultipleCallbackReceived() throws Exception {
final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
new AudioVolumeGroupChangeHandler();
audioAudioVolumeGroupChangedHandler.init();
final int callbackCount = 10;
final List<AudioVolumeGroupCallbackHelper> validCbs =
new ArrayList<AudioVolumeGroupCallbackHelper>();
for (int i = 0; i < callbackCount; i++) {
validCbs.add(new AudioVolumeGroupCallbackHelper());
}
for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
audioAudioVolumeGroupChangedHandler.registerListener(cb);
}
List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
assertTrue(audioVolumeGroups.size() > 0);
try {
for (final AudioVolumeGroup audioVolumeGroup : audioVolumeGroups) {
int volumeGroupId = audioVolumeGroup.getId();
List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes();
// Set the volume per attributes (if valid) and wait the callback
if (avgAttributes.size() == 0 || avgAttributes.get(0).equals(sDefaultAttributes)) {
// Some volume groups may not have valid attributes, used for internal
// volume management like patch/rerouting
// so bailing out strategy retrieval from attributes
continue;
}
AudioAttributes aa = avgAttributes.get(0);
int index = mAudioManager.getVolumeIndexForAttributes(aa);
int indexMax = mAudioManager.getMaxVolumeIndexForAttributes(aa);
int indexMin = mAudioManager.getMinVolumeIndexForAttributes(aa);
final int indexForAa = incrementVolumeIndex(index, indexMin, indexMax);
// Set the receiver to filter only the current group callback
for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
cb.setExpectedVolumeGroup(volumeGroupId);
}
mAudioManager.setVolumeIndexForAttributes(aa, indexForAa, 0/*flags*/);
for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
assertTrue(cb.waitForExpectedVolumeGroupChanged(
AudioVolumeGroupCallbackHelper.ASYNC_TIMEOUT_MS));
}
int readIndex = mAudioManager.getVolumeIndexForAttributes(aa);
assertEquals(readIndex, indexForAa);
}
} finally {
for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
audioAudioVolumeGroupChangedHandler.unregisterListener(cb);
}
}
}
}

View File

@ -0,0 +1,131 @@
/*
* Copyright (C) 2020 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 com.android.audiopolicytest;
import static org.junit.Assert.assertNotEquals;
import android.media.AudioAttributes;
import android.media.AudioSystem;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
import java.util.List;
public class AudioVolumeGroupTest extends AudioVolumesTestBase {
private static final String TAG = "AudioVolumeGroupTest";
//-----------------------------------------------------------------
// Test getAudioVolumeGroups and validate groud id
//-----------------------------------------------------------------
public void testGetVolumeGroupsFromNonServiceCaller() throws Exception {
// The transaction behind getAudioVolumeGroups will fail. Check is done at binder level
// with policy service. Error is not reported, the list is just empty.
// Request must come from service components
List<AudioVolumeGroup> audioVolumeGroup = AudioVolumeGroup.getAudioVolumeGroups();
assertNotNull(audioVolumeGroup);
assertEquals(audioVolumeGroup.size(), 0);
}
//-----------------------------------------------------------------
// Test getAudioVolumeGroups and validate groud id
//-----------------------------------------------------------------
public void testGetVolumeGroups() throws Exception {
// Through AudioManager, the transaction behind getAudioVolumeGroups will succeed
final List<AudioVolumeGroup> audioVolumeGroup = mAudioManager.getAudioVolumeGroups();
assertNotNull(audioVolumeGroup);
assertTrue(audioVolumeGroup.size() > 0);
final List<AudioProductStrategy> audioProductStrategies =
mAudioManager.getAudioProductStrategies();
assertTrue(audioProductStrategies.size() > 0);
for (final AudioVolumeGroup avg : audioVolumeGroup) {
int avgId = avg.getId();
assertNotEquals(avgId, AudioVolumeGroup.DEFAULT_VOLUME_GROUP);
List<AudioAttributes> avgAttributes = avg.getAudioAttributes();
assertNotNull(avgAttributes);
final int[] avgStreamTypes = avg.getLegacyStreamTypes();
assertNotNull(avgStreamTypes);
// for each volume group attributes, find the matching product strategy and ensure
// it is linked the considered volume group
for (final AudioAttributes aa : avgAttributes) {
if (aa.equals(sDefaultAttributes)) {
// Some volume groups may not have valid attributes, used for internal
// volume management like patch/rerouting
// so bailing out strategy retrieval from attributes
continue;
}
boolean isVolumeGroupAssociatedToStrategy = false;
for (final AudioProductStrategy aps : audioProductStrategies) {
int groupId = aps.getVolumeGroupIdForAudioAttributes(aa);
if (groupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
// Note that Audio Product Strategies are priority ordered, and the
// the first one matching the AudioAttributes will be used to identify
// the volume group associated to the request.
assertTrue(aps.supportsAudioAttributes(aa));
assertEquals("Volume Group ID (" + avg.toString()
+ "), and Volume group ID associated to Strategy ("
+ aps.toString() + ") both supporting attributes "
+ aa.toString() + " are mismatching",
avgId, groupId);
isVolumeGroupAssociatedToStrategy = true;
break;
}
}
assertTrue("Volume Group (" + avg.toString()
+ ") has no associated strategy for attributes " + aa.toString(),
isVolumeGroupAssociatedToStrategy);
}
// for each volume group stream type, find the matching product strategy and ensure
// it is linked the considered volume group
for (final int avgStreamType : avgStreamTypes) {
if (avgStreamType == AudioSystem.STREAM_DEFAULT) {
// Some Volume Groups may not have corresponding stream types as they
// intends to address volume setting per attributes to avoid adding new
// stream type and going on deprecating the stream type even for volume
// so bailing out strategy retrieval from stream type
continue;
}
boolean isVolumeGroupAssociatedToStrategy = false;
for (final AudioProductStrategy aps : audioProductStrategies) {
int groupId = aps.getVolumeGroupIdForLegacyStreamType(avgStreamType);
if (groupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
assertEquals("Volume Group ID (" + avg.toString()
+ "), and Volume group ID associated to Strategy ("
+ aps.toString() + ") both supporting stream "
+ AudioSystem.streamToString(avgStreamType) + "("
+ avgStreamType + ") are mismatching",
avgId, groupId);
isVolumeGroupAssociatedToStrategy = true;
break;
}
}
assertTrue("Volume Group (" + avg.toString()
+ ") has no associated strategy for stream "
+ AudioSystem.streamToString(avgStreamType) + "(" + avgStreamType + ")",
isVolumeGroupAssociatedToStrategy);
}
}
}
}

View File

@ -0,0 +1,146 @@
/*
* Copyright (C) 2020 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 com.android.audiopolicytest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
import android.test.ActivityInstrumentationTestCase2;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AudioVolumesTestBase extends ActivityInstrumentationTestCase2<AudioPolicyTest> {
public AudioManager mAudioManager;
Context mContext;
private Map<Integer, Integer> mOriginalStreamVolumes = new HashMap<>();
private Map<Integer, Integer> mOriginalVolumeGroupVolumes = new HashMap<>();
// Default matches the invalid (empty) attributes from native.
// The difference is the input source default which is not aligned between native and java
public static final AudioAttributes sDefaultAttributes =
AudioProductStrategy.sDefaultAttributes;
public static final AudioAttributes sInvalidAttributes = new AudioAttributes.Builder().build();
public final int[] PUBLIC_STREAM_TYPES = { AudioManager.STREAM_VOICE_CALL,
AudioManager.STREAM_SYSTEM, AudioManager.STREAM_RING, AudioManager.STREAM_MUSIC,
AudioManager.STREAM_ALARM, AudioManager.STREAM_NOTIFICATION,
AudioManager.STREAM_DTMF, AudioManager.STREAM_ACCESSIBILITY };
public AudioVolumesTestBase() {
super("com.android.audiopolicytest", AudioPolicyTest.class);
}
/**
* <p>Note: must be called with shell permission (MODIFY_AUDIO_ROUTING)
*/
private void storeAllVolumes() {
List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
for (final AudioVolumeGroup avg : audioVolumeGroups) {
if (avg.getAudioAttributes().isEmpty()) {
// some volume group may not supports volume control per attributes
// like rerouting/patch since these groups are internal to audio policy manager
continue;
}
AudioAttributes avgAttributes = sDefaultAttributes;
for (final AudioAttributes aa : avg.getAudioAttributes()) {
if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) {
avgAttributes = aa;
break;
}
}
if (avgAttributes.equals(sDefaultAttributes)) {
// This shall not happen, however, not purpose of this base class.
// so bailing out.
continue;
}
mOriginalVolumeGroupVolumes.put(
avg.getId(), mAudioManager.getVolumeIndexForAttributes(avgAttributes));
}
}
/**
* <p>Note: must be called with shell permission (MODIFY_AUDIO_ROUTING)
*/
private void restoreAllVolumes() {
List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
for (Map.Entry<Integer, Integer> e : mOriginalVolumeGroupVolumes.entrySet()) {
for (final AudioVolumeGroup avg : audioVolumeGroups) {
if (avg.getId() == e.getKey()) {
assertTrue(!avg.getAudioAttributes().isEmpty());
AudioAttributes avgAttributes = sDefaultAttributes;
for (final AudioAttributes aa : avg.getAudioAttributes()) {
if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) {
avgAttributes = aa;
break;
}
}
assertTrue(!avgAttributes.equals(sDefaultAttributes));
mAudioManager.setVolumeIndexForAttributes(
avgAttributes, e.getValue(), AudioManager.FLAG_ALLOW_RINGER_MODES);
}
}
}
}
@Override
protected void setUp() throws Exception {
super.setUp();
mContext = getActivity();
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
assertEquals(PackageManager.PERMISSION_GRANTED,
mContext.checkSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING));
// Store the original volumes that that they can be recovered in tearDown().
mOriginalStreamVolumes.clear();
for (int streamType : PUBLIC_STREAM_TYPES) {
mOriginalStreamVolumes.put(streamType, mAudioManager.getStreamVolume(streamType));
}
// Store the original volume per attributes so that they can be recovered in tearDown()
mOriginalVolumeGroupVolumes.clear();
storeAllVolumes();
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
// Recover the volume and the ringer mode that the test may have overwritten.
for (Map.Entry<Integer, Integer> e : mOriginalStreamVolumes.entrySet()) {
mAudioManager.setStreamVolume(e.getKey(), e.getValue(),
AudioManager.FLAG_ALLOW_RINGER_MODES);
}
// Recover the original volume per attributes
restoreAllVolumes();
}
public static int resetVolumeIndex(int indexMin, int indexMax) {
return (indexMax + indexMin) / 2;
}
public static int incrementVolumeIndex(int index, int indexMin, int indexMax) {
return (index + 1 > indexMax) ? resetVolumeIndex(indexMin, indexMax) : ++index;
}
}