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

Change-Id: I43a563327bd59963cb89a8f068076e98224449f6
diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp
new file mode 100644
index 0000000..ed338375
--- /dev/null
+++ b/media/tests/AudioPolicyTest/Android.bp
@@ -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"],
+}
diff --git a/media/tests/AudioPolicyTest/AndroidManifest.xml b/media/tests/AudioPolicyTest/AndroidManifest.xml
new file mode 100644
index 0000000..adb058c
--- /dev/null
+++ b/media/tests/AudioPolicyTest/AndroidManifest.xml
@@ -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>
diff --git a/media/tests/AudioPolicyTest/AndroidTest.xml b/media/tests/AudioPolicyTest/AndroidTest.xml
new file mode 100644
index 0000000..f3ca9a1
--- /dev/null
+++ b/media/tests/AudioPolicyTest/AndroidTest.xml
@@ -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>
diff --git a/media/tests/AudioPolicyTest/res/layout/audiopolicytest.xml b/media/tests/AudioPolicyTest/res/layout/audiopolicytest.xml
new file mode 100644
index 0000000..17fdba6
--- /dev/null
+++ b/media/tests/AudioPolicyTest/res/layout/audiopolicytest.xml
@@ -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>
diff --git a/media/tests/AudioPolicyTest/res/values/strings.xml b/media/tests/AudioPolicyTest/res/values/strings.xml
new file mode 100644
index 0000000..0365927
--- /dev/null
+++ b/media/tests/AudioPolicyTest/res/values/strings.xml
@@ -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>
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java
new file mode 100644
index 0000000..1131c62
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java
@@ -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);
+        }
+    }
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTest.java
new file mode 100644
index 0000000..e0c7b22
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTest.java
@@ -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();
+    }
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java
new file mode 100644
index 0000000..c0f596b
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java
@@ -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);
+            }
+        }
+    }
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupCallbackHelper.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupCallbackHelper.java
new file mode 100644
index 0000000..0c1d52c
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupCallbackHelper.java
@@ -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;
+    }
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java
new file mode 100644
index 0000000..221f1f7
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java
@@ -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);
+            }
+        }
+    }
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupTest.java
new file mode 100644
index 0000000..84b24b8
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupTest.java
@@ -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);
+            }
+        }
+    }
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestBase.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestBase.java
new file mode 100644
index 0000000..a17d65c
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestBase.java
@@ -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;
+    }
+}