AudioVolumeGroup introspection & callback JAVA APIs

This CL adds introspection APIs for Audio Volume group
and callback notification on group volume change.
It allows to keep the same level of service available today
in CarAudioManager and is a starting point to get rid of Stream Alias
hard coded in AudioServer.

Bug: 124767636
Test: dumpsys media.audio_policy
Change-Id: I48909df93c146f071acd24cd3ecc608cb98b4d7e
Signed-off-by: François Gaffie <francois.gaffie@renault.com>
This commit is contained in:
François Gaffie
2018-09-18 17:06:26 +02:00
committed by Hongwei Wang
parent 4ed935a5aa
commit adcd00a7c8
15 changed files with 1091 additions and 27 deletions

View File

@@ -3456,15 +3456,18 @@ package android.media {
method public void clearAudioServerStateCallback();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.audiopolicy.AudioProductStrategies getAudioProductStrategies();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.audiopolicy.AudioVolumeGroups getAudioVolumeGroups();
method public boolean isAudioServerRunning();
method public boolean isHdmiSystemAudioSupported();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int requestAudioFocus(@NonNull android.media.AudioFocusRequest, @Nullable android.media.audiopolicy.AudioPolicy);
method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
method public void unregisterVolumeGroupCallback(@NonNull android.media.AudioManager.VolumeGroupCallback);
field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
field public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 2; // 0x2
@@ -3478,6 +3481,11 @@ package android.media {
method public void onAudioServerUp();
}
public abstract static class AudioManager.VolumeGroupCallback {
ctor public AudioManager.VolumeGroupCallback();
method public void onAudioVolumeGroupChanged(int, int);
}
public final class AudioPlaybackConfiguration implements android.os.Parcelable {
method public int getClientPid();
method public int getClientUid();
@@ -3649,6 +3657,8 @@ package android.media.audiopolicy {
method @Nullable public android.media.audiopolicy.AudioProductStrategy getById(int);
method public int getLegacyStreamTypeForAudioAttributes(@NonNull android.media.AudioAttributes);
method @Nullable public android.media.audiopolicy.AudioProductStrategy getProductStrategyForAudioAttributes(@NonNull android.media.AudioAttributes);
method public int getVolumeGroupIdForAttributes(@NonNull android.media.AudioAttributes);
method public int getVolumeGroupIdForLegacyStreamType(int);
method public java.util.Iterator<android.media.audiopolicy.AudioProductStrategy> iterator();
method public int size();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -3664,6 +3674,27 @@ package android.media.audiopolicy {
field public static final android.os.Parcelable.Creator<android.media.audiopolicy.AudioProductStrategy> CREATOR;
}
public final class AudioVolumeGroup implements android.os.Parcelable {
method public int describeContents();
method public java.util.List<android.media.AudioAttributes> getAudioAttributes();
method public int getId();
method @NonNull public int[] getLegacyStreamTypes();
method @NonNull public String name();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.media.audiopolicy.AudioVolumeGroup> CREATOR;
}
public final class AudioVolumeGroups implements java.lang.Iterable<android.media.audiopolicy.AudioVolumeGroup> android.os.Parcelable {
ctor public AudioVolumeGroups();
method public int describeContents();
method @Nullable public android.media.audiopolicy.AudioVolumeGroup getById(int);
method public java.util.Iterator<android.media.audiopolicy.AudioVolumeGroup> iterator();
method public int size();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.media.audiopolicy.AudioVolumeGroups> CREATOR;
field public static final int DEFAULT_VOLUME_GROUP = -1; // 0xffffffff
}
}
package android.media.session {

View File

@@ -165,6 +165,8 @@ cc_library_shared {
"android_media_AudioTrack.cpp",
"android_media_AudioAttributes.cpp",
"android_media_AudioProductStrategies.cpp",
"android_media_AudioVolumeGroups.cpp",
"android_media_AudioVolumeGroupCallback.cpp",
"android_media_DeviceCallback.cpp",
"android_media_JetPlayer.cpp",
"android_media_MediaMetricsJNI.cpp",

View File

@@ -110,6 +110,8 @@ extern int register_android_media_AudioSystem(JNIEnv *env);
extern int register_android_media_AudioTrack(JNIEnv *env);
extern int register_android_media_AudioAttributes(JNIEnv *env);
extern int register_android_media_AudioProductStrategies(JNIEnv *env);
extern int register_android_media_AudioVolumeGroups(JNIEnv *env);
extern int register_android_media_AudioVolumeGroupChangeHandler(JNIEnv *env);
extern int register_android_media_MicrophoneInfo(JNIEnv *env);
extern int register_android_media_JetPlayer(JNIEnv *env);
extern int register_android_media_ToneGenerator(JNIEnv *env);
@@ -1524,6 +1526,8 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_media_AudioTrack),
REG_JNI(register_android_media_AudioAttributes),
REG_JNI(register_android_media_AudioProductStrategies),
REG_JNI(register_android_media_AudioVolumeGroups),
REG_JNI(register_android_media_AudioVolumeGroupChangeHandler),
REG_JNI(register_android_media_JetPlayer),
REG_JNI(register_android_media_MicrophoneInfo),
REG_JNI(register_android_media_RemoteDisplay),

View File

@@ -57,7 +57,7 @@ static struct {
static jclass gAudioAttributesGroupClass;
static jmethodID gAudioAttributesGroupCstor;
static struct {
jfieldID mGroupId;
jfieldID mVolumeGroupId;
jfieldID mLegacyStreamType;
jfieldID mAudioAttributes;
} gAudioAttributesGroupsFields;
@@ -207,7 +207,7 @@ android_media_AudioSystem_getProductStrategyFromAudioAttributes(JNIEnv *env, job
}
product_strategy_t psId;
status_t status = AudioSystem::getProductStrategyFromAudioAttributes(
AudioAttributes(*attributes.get()), psId);
AudioAttributes(*attributes.get()), psId);
if (status != NO_ERROR) {
return nativeToJavaStatus(status);
}
@@ -249,8 +249,8 @@ int register_android_media_AudioProductStrategies(JNIEnv *env)
gAudioAttributesGroupClass = MakeGlobalRefOrDie(env, audioAttributesGroupClass);
gAudioAttributesGroupCstor = GetMethodIDOrDie(env, audioAttributesGroupClass, "<init>",
"(II[Landroid/media/AudioAttributes;)V");
gAudioAttributesGroupsFields.mGroupId = GetFieldIDOrDie(
env, audioAttributesGroupClass, "mGroupId", "I");
gAudioAttributesGroupsFields.mVolumeGroupId = GetFieldIDOrDie(
env, audioAttributesGroupClass, "mVolumeGroupId", "I");
gAudioAttributesGroupsFields.mLegacyStreamType = GetFieldIDOrDie(
env, audioAttributesGroupClass, "mLegacyStreamType", "I");
gAudioAttributesGroupsFields.mAudioAttributes = GetFieldIDOrDie(

View File

@@ -0,0 +1,175 @@
/*
* Copyright (C) 2018 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.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "AudioVolumeGroupCallback-JNI"
#include <utils/Log.h>
#include <nativehelper/JNIHelp.h>
#include "core_jni_helpers.h"
#include "android_media_AudioVolumeGroupCallback.h"
// ----------------------------------------------------------------------------
using namespace android;
static const char* const kAudioVolumeGroupChangeHandlerClassPathName =
"android/media/audiopolicy/AudioVolumeGroupChangeHandler";
static struct {
jfieldID mJniCallback;
} gAudioVolumeGroupChangeHandlerFields;
static struct {
jmethodID postEventFromNative;
} gAudioVolumeGroupChangeHandlerMethods;
static Mutex gLock;
JNIAudioVolumeGroupCallback::JNIAudioVolumeGroupCallback(JNIEnv* env,
jobject thiz,
jobject weak_thiz)
{
jclass clazz = env->GetObjectClass(thiz);
if (clazz == NULL) {
ALOGE("Can't find class %s", kAudioVolumeGroupChangeHandlerClassPathName);
return;
}
mClass = (jclass)env->NewGlobalRef(clazz);
// We use a weak reference so the AudioVolumeGroupChangeHandler object can be garbage collected.
// The reference is only used as a proxy for callbacks.
mObject = env->NewGlobalRef(weak_thiz);
}
JNIAudioVolumeGroupCallback::~JNIAudioVolumeGroupCallback()
{
// remove global references
JNIEnv *env = AndroidRuntime::getJNIEnv();
if (env == NULL) {
return;
}
env->DeleteGlobalRef(mObject);
env->DeleteGlobalRef(mClass);
}
void JNIAudioVolumeGroupCallback::onAudioVolumeGroupChanged(volume_group_t group, int flags)
{
JNIEnv *env = AndroidRuntime::getJNIEnv();
if (env == NULL) {
return;
}
ALOGV("%s volume group id %d", __FUNCTION__, group);
env->CallStaticVoidMethod(mClass,
gAudioVolumeGroupChangeHandlerMethods.postEventFromNative,
mObject,
AUDIOVOLUMEGROUP_EVENT_VOLUME_CHANGED, group, flags, NULL);
if (env->ExceptionCheck()) {
ALOGW("An exception occurred while notifying an event.");
env->ExceptionClear();
}
}
void JNIAudioVolumeGroupCallback::onServiceDied()
{
JNIEnv *env = AndroidRuntime::getJNIEnv();
if (env == NULL) {
return;
}
env->CallStaticVoidMethod(mClass,
gAudioVolumeGroupChangeHandlerMethods.postEventFromNative,
mObject,
AUDIOVOLUMEGROUP_EVENT_SERVICE_DIED, 0, 0, NULL);
if (env->ExceptionCheck()) {
ALOGW("An exception occurred while notifying an event.");
env->ExceptionClear();
}
}
static
sp<JNIAudioVolumeGroupCallback> setJniCallback(JNIEnv* env,
jobject thiz,
const sp<JNIAudioVolumeGroupCallback>& callback)
{
Mutex::Autolock l(gLock);
sp<JNIAudioVolumeGroupCallback> old = (JNIAudioVolumeGroupCallback*)env->GetLongField(
thiz, gAudioVolumeGroupChangeHandlerFields.mJniCallback);
if (callback.get()) {
callback->incStrong((void*)setJniCallback);
}
if (old != 0) {
old->decStrong((void*)setJniCallback);
}
env->SetLongField(thiz, gAudioVolumeGroupChangeHandlerFields.mJniCallback,
(jlong)callback.get());
return old;
}
static void
android_media_AudioVolumeGroupChangeHandler_eventHandlerSetup(JNIEnv *env,
jobject thiz,
jobject weak_this)
{
ALOGV("%s", __FUNCTION__);
sp<JNIAudioVolumeGroupCallback> callback =
new JNIAudioVolumeGroupCallback(env, thiz, weak_this);
if (AudioSystem::addAudioVolumeGroupCallback(callback) == NO_ERROR) {
setJniCallback(env, thiz, callback);
}
}
static void
android_media_AudioVolumeGroupChangeHandler_eventHandlerFinalize(JNIEnv *env, jobject thiz)
{
ALOGV("%s", __FUNCTION__);
sp<JNIAudioVolumeGroupCallback> callback = setJniCallback(env, thiz, 0);
if (callback != 0) {
AudioSystem::removeAudioVolumeGroupCallback(callback);
}
}
/*
* JNI registration.
*/
static const JNINativeMethod gMethods[] = {
{"native_setup", "(Ljava/lang/Object;)V",
(void *)android_media_AudioVolumeGroupChangeHandler_eventHandlerSetup},
{"native_finalize", "()V",
(void *)android_media_AudioVolumeGroupChangeHandler_eventHandlerFinalize},
};
int register_android_media_AudioVolumeGroupChangeHandler(JNIEnv *env)
{
jclass audioVolumeGroupChangeHandlerClass =
FindClassOrDie(env, kAudioVolumeGroupChangeHandlerClassPathName);
gAudioVolumeGroupChangeHandlerMethods.postEventFromNative =
GetStaticMethodIDOrDie(env, audioVolumeGroupChangeHandlerClass, "postEventFromNative",
"(Ljava/lang/Object;IIILjava/lang/Object;)V");
gAudioVolumeGroupChangeHandlerFields.mJniCallback =
GetFieldIDOrDie(env, audioVolumeGroupChangeHandlerClass, "mJniCallback", "J");
env->DeleteLocalRef(audioVolumeGroupChangeHandlerClass);
return RegisterMethodsOrDie(env,
kAudioVolumeGroupChangeHandlerClassPathName,
gMethods,
NELEM(gMethods));
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2018 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.
*/
#pragma once
#include <system/audio.h>
#include <media/AudioSystem.h>
namespace android {
// keep in sync with AudioManager.AudioVolumeGroupChangeHandler.java
#define AUDIOVOLUMEGROUP_EVENT_VOLUME_CHANGED 1000
#define AUDIOVOLUMEGROUP_EVENT_SERVICE_DIED 1001
class JNIAudioVolumeGroupCallback: public AudioSystem::AudioVolumeGroupCallback
{
public:
JNIAudioVolumeGroupCallback(JNIEnv* env, jobject thiz, jobject weak_thiz);
~JNIAudioVolumeGroupCallback();
void onAudioVolumeGroupChanged(volume_group_t group, int flags) override;
void onServiceDied() override;
private:
void sendEvent(int event);
jclass mClass; /**< Reference to AudioVolumeGroupChangeHandler class. */
jobject mObject; /**< Weak ref to AudioVolumeGroupChangeHandler object to call on. */
};
} // namespace android

View File

@@ -0,0 +1,183 @@
/*
* Copyright (C) 2018 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.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "AudioVolumeGroups-JNI"
#include <inttypes.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include "core_jni_helpers.h"
#include <utils/Log.h>
#include <vector>
#include <media/AudioSystem.h>
#include <media/AudioPolicy.h>
#include <nativehelper/ScopedUtfChars.h>
#include "android_media_AudioAttributes.h"
#include "android_media_AudioErrors.h"
// ----------------------------------------------------------------------------
using namespace android;
// ----------------------------------------------------------------------------
static const char* const kClassPathName = "android/media/audiopolicy/AudioVolumeGroups";
static const char* const kAudioVolumeGroupClassPathName =
"android/media/audiopolicy/AudioVolumeGroup";
static jclass gAudioVolumeGroupClass;
static jmethodID gAudioVolumeGroupCstor;
static struct {
jfieldID mName;
jfieldID mId;
} gAudioVolumeGroupFields;
static jclass gArrayListClass;
static jmethodID gArrayListCstor;
static struct {
jmethodID add;
jmethodID toArray;
} gArrayListMethods;
static jint convertAudioVolumeGroupsFromNative(
JNIEnv *env, jobject *jGroup, const AudioVolumeGroup &group)
{
jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
jstring jName = NULL;
jint Id = NULL;
jintArray jLegacyStreamTypes = NULL;
jobjectArray jAudioAttributes = NULL;
jint numAttributes;
jobject jAudioAttribute = NULL;
jName = env->NewStringUTF(group.getName().c_str());
Id = static_cast<jint>(group.getId());
// Legacy stream types array
jLegacyStreamTypes = env->NewIntArray(group.getStreamTypes().size());
if (jLegacyStreamTypes == NULL) {
jStatus = (jint)AUDIO_JAVA_ERROR;
goto exit;
}
for (size_t streamIndex = 0; streamIndex < group.getStreamTypes().size(); streamIndex++) {
jint jStream = group.getStreamTypes()[streamIndex];
env->SetIntArrayRegion(jLegacyStreamTypes, streamIndex, 1, &jStream);
}
// Audio Attributes array
numAttributes = group.getAudioAttributes().size();
jStatus = JNIAudioAttributeHelper::getJavaArray(env, &jAudioAttributes, numAttributes);
if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
goto exit;
}
for (size_t j = 0; j < static_cast<size_t>(numAttributes); j++) {
auto attributes = group.getAudioAttributes()[j];
jStatus = JNIAudioAttributeHelper::nativeToJava(env, &jAudioAttribute, attributes);
if (jStatus != AUDIO_JAVA_SUCCESS) {
goto exit;
}
env->SetObjectArrayElement(jAudioAttributes, j, jAudioAttribute);
}
*jGroup = env->NewObject(gAudioVolumeGroupClass, gAudioVolumeGroupCstor,
jName, Id, jAudioAttributes, jLegacyStreamTypes);
exit:
if (jName != NULL) {
env->DeleteLocalRef(jName);
}
return jStatus;
}
static jint
android_media_AudioSystem_listAudioVolumeGroups(JNIEnv *env, jobject clazz, jobject jVolumeGroups)
{
if (env == NULL) {
return AUDIO_JAVA_DEAD_OBJECT;
}
if (jVolumeGroups == NULL) {
ALOGE("listAudioVolumeGroups NULL AudioVolumeGroups");
return (jint)AUDIO_JAVA_BAD_VALUE;
}
if (!env->IsInstanceOf(jVolumeGroups, gArrayListClass)) {
ALOGE("listAudioVolumeGroups not an arraylist");
return (jint)AUDIO_JAVA_BAD_VALUE;
}
status_t status;
AudioVolumeGroupVector groups;
jint jStatus;
jobject jGroup = NULL;
status = AudioSystem::listAudioVolumeGroups(groups);
if (status != NO_ERROR) {
ALOGE("AudioSystem::listAudioVolumeGroups error %d", status);
return nativeToJavaStatus(status);
}
for (const auto &group : groups) {
jStatus = convertAudioVolumeGroupsFromNative(env, &jGroup, group);
if (jStatus != AUDIO_JAVA_SUCCESS) {
goto exit;
}
env->CallBooleanMethod(jVolumeGroups, gArrayListMethods.add, jGroup);
}
exit:
if (jGroup != NULL) {
env->DeleteLocalRef(jGroup);
}
return jStatus;
}
/*
* JNI registration.
*/
static const JNINativeMethod gMethods[] = {
{"native_list_audio_volume_groups", "(Ljava/util/ArrayList;)I",
(void *)android_media_AudioSystem_listAudioVolumeGroups},
};
int register_android_media_AudioVolumeGroups(JNIEnv *env)
{
jclass arrayListClass = FindClassOrDie(env, "java/util/ArrayList");
gArrayListClass = MakeGlobalRefOrDie(env, arrayListClass);
gArrayListCstor = GetMethodIDOrDie(env, arrayListClass, "<init>", "()V");
gArrayListMethods.add = GetMethodIDOrDie(env, arrayListClass, "add", "(Ljava/lang/Object;)Z");
gArrayListMethods.toArray = GetMethodIDOrDie(env, arrayListClass,
"toArray", "()[Ljava/lang/Object;");
jclass audioVolumeGroupClass = FindClassOrDie(env, kAudioVolumeGroupClassPathName);
gAudioVolumeGroupClass = MakeGlobalRefOrDie(env, audioVolumeGroupClass);
gAudioVolumeGroupCstor = GetMethodIDOrDie(
env, audioVolumeGroupClass, "<init>",
"(Ljava/lang/String;I[Landroid/media/AudioAttributes;[I)V");
gAudioVolumeGroupFields.mName = GetFieldIDOrDie(
env, audioVolumeGroupClass, "mName", "Ljava/lang/String;");
gAudioVolumeGroupFields.mId = GetFieldIDOrDie(
env, audioVolumeGroupClass, "mId", "I");
env->DeleteLocalRef(audioVolumeGroupClass);
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}

View File

@@ -37,6 +37,8 @@ import android.content.Intent;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
import android.media.audiopolicy.AudioProductStrategies;
import android.media.audiopolicy.AudioVolumeGroupChangeHandler;
import android.media.audiopolicy.AudioVolumeGroups;
import android.media.projection.MediaProjection;
import android.media.session.MediaController;
import android.media.session.MediaSession;
@@ -61,6 +63,7 @@ import android.util.Pair;
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -90,6 +93,8 @@ public class AudioManager {
private static final String TAG = "AudioManager";
private static final boolean DEBUG = false;
private static final AudioPortEventHandler sAudioPortEventHandler = new AudioPortEventHandler();
private static final AudioVolumeGroupChangeHandler sAudioAudioVolumeGroupChangedHandler =
new AudioVolumeGroupChangeHandler();
/**
* Broadcast intent, a hint for applications that audio is about to become
@@ -5246,6 +5251,60 @@ public class AudioManager {
}
}
/**
* @hide
* Introspection API to retrieve audio volume groups.
* When implementing {Car|Oem}AudioManager, use this method to retrieve the collection of
* audio volume groups.
* @return a (possibly zero-length) array of
* {@see android.media.audiopolicy.AudioVolumeGroups} objects.
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public @NonNull AudioVolumeGroups getAudioVolumeGroups() {
return new AudioVolumeGroups();
}
/**
* @hide
* Callback registered by client to be notified upon volume group change.
*/
@SystemApi
public abstract static class VolumeGroupCallback {
/**
* Callback method called upon audio volume group change.
* @param group the group for which the volume has changed
*/
public void onAudioVolumeGroupChanged(int group, int flags) {}
}
/**
* @hide
* Register an audio volume group change listener.
* @param callback the {@link VolumeGroupCallback} to register
*/
@SystemApi
public void registerVolumeGroupCallback(
@NonNull Executor executor,
@NonNull VolumeGroupCallback callback) {
Preconditions.checkNotNull(executor, "executor must not be null");
Preconditions.checkNotNull(callback, "volume group change cb must not be null");
sAudioAudioVolumeGroupChangedHandler.init();
// TODO: make use of executor
sAudioAudioVolumeGroupChangedHandler.registerListener(callback);
}
/**
* @hide
* Unregister an audio volume group change listener.
* @param callback the {@link VolumeGroupCallback} to unregister
*/
@SystemApi
public void unregisterVolumeGroupCallback(
@NonNull VolumeGroupCallback callback) {
Preconditions.checkNotNull(callback, "volume group change cb must not be null");
sAudioAudioVolumeGroupChangedHandler.unregisterListener(callback);
}
//---------------------------------------------------------
// Inner classes

View File

@@ -177,7 +177,50 @@ public final class AudioProductStrategies implements Iterable<AudioProductStrate
@Nullable
public AudioProductStrategy getProductStrategyForAudioAttributes(@NonNull AudioAttributes aa) {
Preconditions.checkNotNull(aa, "attributes must not be null");
return getById(native_get_product_strategies_from_audio_attributes(aa));
int productStrategyId = native_get_product_strategies_from_audio_attributes(aa);
if (productStrategyId < 0) {
Log.w(TAG, "no strategy found for Attributes " + aa.toString());
return null;
}
return getById(productStrategyId);
}
/**
* @hide
* @param attributes the {@link AudioAttributes} to be considered
* @return volume group associated to the given {@link AudioAttributes}.
* If no group supports the given {@link AudioAttributes}, it returns the volume group
* for the default attributes.
* If no group supports the default attributes, it returns {@link #DEFAULT_VOLUME_GROUP}
*/
@SystemApi
public int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) {
Preconditions.checkNotNull(attributes, "attributes must not be null");
int volumeGroupId = getVolumeGroupIdForAttributesInt(attributes);
if (volumeGroupId != AudioVolumeGroups.DEFAULT_VOLUME_GROUP) {
return volumeGroupId;
}
// The default volume group is the one hosted by default product strategy, i.e.
// supporting Default Attributes
return getVolumeGroupIdForAttributesInt(AudioProductStrategy.sDefaultAttributes);
}
/**
* @hide
* @param streamType to be considered
* @return volume group associated to the given stream type.
*/
@SystemApi
public int getVolumeGroupIdForLegacyStreamType(int streamType) {
for (final AudioProductStrategy productStrategy : this) {
int volumeGroupId = productStrategy.getVolumeGroupIdForLegacyStreamType(streamType);
if (volumeGroupId != AudioVolumeGroups.DEFAULT_VOLUME_GROUP) {
return volumeGroupId;
}
}
// The default volume group is the one hosted by default product strategy, i.e.
// supporting Default Attributes
return getVolumeGroupIdForAttributesInt(AudioProductStrategy.sDefaultAttributes);
}
@Override
@@ -193,6 +236,21 @@ public final class AudioProductStrategies implements Iterable<AudioProductStrate
}
}
/**
* @param attributes to be considered
* @return volume group associated to the given {@link AudioAttributes}.
*/
private int getVolumeGroupIdForAttributesInt(@NonNull AudioAttributes attributes) {
Preconditions.checkNotNull(attributes, "attributes must not be null");
for (final AudioProductStrategy productStrategy : this) {
int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes);
if (volumeGroupId != AudioVolumeGroups.DEFAULT_VOLUME_GROUP) {
return volumeGroupId;
}
}
return AudioVolumeGroups.DEFAULT_VOLUME_GROUP;
}
public static final Parcelable.Creator<AudioProductStrategies> CREATOR =
new Parcelable.Creator<AudioProductStrategies>() {
@Override

View File

@@ -157,32 +157,32 @@ public final class AudioProductStrategy implements Parcelable {
/**
* @hide
* @param streamType legacy stream type used for volume operation only
* @return the {@link AudioAttributes} relevant for the given streamType.
* If none is found, it builds the default attributes.
* @return the volume group id relevant for the given streamType.
* If none is found, {@link AudioVolumeGroups#DEFAULT_VOLUME_GROUP} is returned.
*/
public int getGroupIdForLegacyStreamType(int streamType) {
public int getVolumeGroupIdForLegacyStreamType(int streamType) {
for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
if (aag.supportsStreamType(streamType)) {
return aag.getGroupId();
return aag.getVolumeGroupId();
}
}
return DEFAULT_GROUP;
return AudioVolumeGroups.DEFAULT_VOLUME_GROUP;
}
/**
* @hide
* @param aa the {@link AudioAttributes} to be considered
* @return the group id associated with the given audio attributes if found,
* default value otherwise.
* @return the volume group id associated with the given audio attributes if found,
* {@link AudioVolumeGroups#DEFAULT_VOLUME_GROUP} otherwise.
*/
public int getGroupIdForAudioAttributes(@NonNull AudioAttributes aa) {
public int getVolumeGroupIdForAudioAttributes(@NonNull AudioAttributes aa) {
Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
if (aag.supportsAttributes(aa)) {
return aag.getGroupId();
return aag.getVolumeGroupId();
}
}
return DEFAULT_GROUP;
return AudioVolumeGroups.DEFAULT_VOLUME_GROUP;
}
@Override
@@ -266,15 +266,14 @@ public final class AudioProductStrategy implements Parcelable {
&& ((refFormattedTags.length() == 0) || refFormattedTags.equals(cliFormattedTags));
}
private static final class AudioAttributesGroup implements Parcelable {
private int mGroupId;
private int mVolumeGroupId;
private int mLegacyStreamType;
private final AudioAttributes[] mAudioAttributes;
AudioAttributesGroup(int groupId, int streamType,
AudioAttributesGroup(int volumeGroupId, int streamType,
@NonNull AudioAttributes[] audioAttributes) {
mGroupId = groupId;
mVolumeGroupId = volumeGroupId;
mLegacyStreamType = streamType;
mAudioAttributes = audioAttributes;
}
@@ -286,7 +285,7 @@ public final class AudioProductStrategy implements Parcelable {
AudioAttributesGroup thatAag = (AudioAttributesGroup) o;
return mGroupId == thatAag.mGroupId
return mVolumeGroupId == thatAag.mVolumeGroupId
&& mLegacyStreamType == thatAag.mLegacyStreamType
&& mAudioAttributes.equals(thatAag.mAudioAttributes);
}
@@ -295,8 +294,8 @@ public final class AudioProductStrategy implements Parcelable {
return mLegacyStreamType;
}
public int getGroupId() {
return mGroupId;
public int getVolumeGroupId() {
return mVolumeGroupId;
}
public @NonNull AudioAttributes getAudioAttributes() {
@@ -331,7 +330,7 @@ public final class AudioProductStrategy implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mGroupId);
dest.writeInt(mVolumeGroupId);
dest.writeInt(mLegacyStreamType);
dest.writeInt(mAudioAttributes.length);
for (AudioAttributes attributes : mAudioAttributes) {
@@ -343,14 +342,14 @@ public final class AudioProductStrategy implements Parcelable {
new Parcelable.Creator<AudioAttributesGroup>() {
@Override
public AudioAttributesGroup createFromParcel(@NonNull Parcel in) {
int groupId = in.readInt();
int volumeGroupId = in.readInt();
int streamType = in.readInt();
int nbAttributes = in.readInt();
AudioAttributes[] aa = new AudioAttributes[nbAttributes];
for (int index = 0; index < nbAttributes; index++) {
aa[index] = AudioAttributes.CREATOR.createFromParcel(in);
}
return new AudioAttributesGroup(groupId, streamType, aa);
return new AudioAttributesGroup(volumeGroupId, streamType, aa);
}
@Override
@@ -365,8 +364,8 @@ public final class AudioProductStrategy implements Parcelable {
StringBuilder s = new StringBuilder();
s.append("\n Legacy Stream Type: ");
s.append(Integer.toString(mLegacyStreamType));
s.append(" Group Id: ");
s.append(Integer.toString(mGroupId));
s.append(" Volume Group Id: ");
s.append(Integer.toString(mVolumeGroupId));
for (AudioAttributes attribute : mAudioAttributes) {
s.append("\n -");

View File

@@ -0,0 +1,18 @@
/* Copyright 2018, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
package android.media.audiopolicy;
parcelable AudioVolumeGroup;

View File

@@ -0,0 +1,171 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media.audiopolicy;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.media.AudioAttributes;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
import java.util.Arrays;
import java.util.List;
/**
* A class to create the association between different playback attributes
* (e.g. media, mapping direction) to a single volume control.
* @hide
*/
@SystemApi
public final class AudioVolumeGroup implements Parcelable {
/**
* Unique identifier of a volume group.
*/
private int mId;
/**
* human-readable name of this volume group.
*/
private final String mName;
private final AudioAttributes[] mAudioAttributes;
private int[] mLegacyStreamTypes;
/**
* @param name of the volume group
* @param id of the volume group
* @param followers {@link AudioProductStrategies} strategy following this volume group
*/
AudioVolumeGroup(@NonNull String name, int id,
@NonNull AudioAttributes[] audioAttributes,
@NonNull int[] legacyStreamTypes) {
Preconditions.checkNotNull(name, "name must not be null");
Preconditions.checkNotNull(audioAttributes, "audioAttributes must not be null");
Preconditions.checkNotNull(legacyStreamTypes, "legacyStreamTypes must not be null");
mName = name;
mId = id;
mAudioAttributes = audioAttributes;
mLegacyStreamTypes = legacyStreamTypes;
}
@Override
public boolean equals(@NonNull Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AudioVolumeGroup thatAvg = (AudioVolumeGroup) o;
return mName == thatAvg.mName && mId == thatAvg.mId
&& mAudioAttributes.equals(thatAvg.mAudioAttributes);
}
/**
* @return List of {@link AudioAttributes} involved in this {@link AudioVolumeGroup}.
*/
public List<AudioAttributes> getAudioAttributes() {
return Arrays.asList(mAudioAttributes);
}
/**
* @return the stream types involved in this {@link AudioVolumeGroup}.
*/
public @NonNull int[] getLegacyStreamTypes() {
return mLegacyStreamTypes;
}
/**
* @return human-readable name of this volume group.
*/
public @NonNull String name() {
return mName;
}
/**
* @return the volume group unique identifier id.
*/
public int getId() {
return mId;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mName);
dest.writeInt(mId);
dest.writeInt(mAudioAttributes.length);
for (AudioAttributes attributes : mAudioAttributes) {
attributes.writeToParcel(dest, flags | AudioAttributes.FLATTEN_TAGS/*flags*/);
}
dest.writeInt(mLegacyStreamTypes.length);
for (int streamType : mLegacyStreamTypes) {
dest.writeInt(streamType);
}
}
public static final Parcelable.Creator<AudioVolumeGroup> CREATOR =
new Parcelable.Creator<AudioVolumeGroup>() {
@Override
public @NonNull AudioVolumeGroup createFromParcel(@NonNull Parcel in) {
Preconditions.checkNotNull(in, "in Parcel must not be null");
String name = in.readString();
int id = in.readInt();
int nbAttributes = in.readInt();
AudioAttributes[] audioAttributes = new AudioAttributes[nbAttributes];
for (int index = 0; index < nbAttributes; index++) {
audioAttributes[index] = AudioAttributes.CREATOR.createFromParcel(in);
}
int nbStreamTypes = in.readInt();
int[] streamTypes = new int[nbStreamTypes];
for (int index = 0; index < nbStreamTypes; index++) {
streamTypes[index] = in.readInt();
}
return new AudioVolumeGroup(name, id, audioAttributes, streamTypes);
}
@Override
public @NonNull AudioVolumeGroup[] newArray(int size) {
return new AudioVolumeGroup[size];
}
};
@Override
public @NonNull String toString() {
StringBuilder s = new StringBuilder();
s.append("\n Name: ");
s.append(mName);
s.append(" Id: ");
s.append(Integer.toString(mId));
s.append("\n Supported Audio Attributes:");
for (AudioAttributes attribute : mAudioAttributes) {
s.append("\n -");
s.append(attribute.toString());
}
s.append("\n Supported Legacy Stream Types: { ");
for (int legacyStreamType : mLegacyStreamTypes) {
s.append(Integer.toString(legacyStreamType));
s.append(" ");
}
s.append("}");
return s.toString();
}
}

View File

@@ -0,0 +1,167 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media.audiopolicy;
import android.annotation.NonNull;
import android.media.AudioManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import com.android.internal.util.Preconditions;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
* The AudioVolumeGroupChangeHandler handles AudioManager.OnAudioVolumeGroupChangedListener
* callbacks posted from JNI
*
* TODO: Make use of Executor of callbacks.
* @hide
*/
public class AudioVolumeGroupChangeHandler {
private Handler mHandler;
private HandlerThread mHandlerThread;
private final ArrayList<AudioManager.VolumeGroupCallback> mListeners =
new ArrayList<AudioManager.VolumeGroupCallback>();
private static final String TAG = "AudioVolumeGroupChangeHandler";
private static final int AUDIOVOLUMEGROUP_EVENT_VOLUME_CHANGED = 1000;
private static final int AUDIOVOLUMEGROUP_EVENT_NEW_LISTENER = 4;
/**
* Accessed by native methods: JNI Callback context.
*/
@SuppressWarnings("unused")
private long mJniCallback;
/**
* Initialization
*/
public void init() {
synchronized (this) {
if (mHandler != null) {
return;
}
// create a new thread for our new event handler
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
if (mHandlerThread.getLooper() == null) {
mHandler = null;
return;
}
mHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
ArrayList<AudioManager.VolumeGroupCallback> listeners;
synchronized (this) {
if (msg.what == AUDIOVOLUMEGROUP_EVENT_NEW_LISTENER) {
listeners =
new ArrayList<AudioManager.VolumeGroupCallback>();
if (mListeners.contains(msg.obj)) {
listeners.add(
(AudioManager.VolumeGroupCallback) msg.obj);
}
} else {
listeners = mListeners;
}
}
if (listeners.isEmpty()) {
return;
}
switch (msg.what) {
case AUDIOVOLUMEGROUP_EVENT_VOLUME_CHANGED:
for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).onAudioVolumeGroupChanged((int) msg.arg1,
(int) msg.arg2);
}
break;
default:
break;
}
}
};
native_setup(new WeakReference<AudioVolumeGroupChangeHandler>(this));
}
}
private native void native_setup(Object moduleThis);
@Override
protected void finalize() {
native_finalize();
if (mHandlerThread.isAlive()) {
mHandlerThread.quit();
}
}
private native void native_finalize();
/**
* @param cb the {@link AudioManager.VolumeGroupCallback} to register
*/
public void registerListener(@NonNull AudioManager.VolumeGroupCallback cb) {
Preconditions.checkNotNull(cb, "volume group callback shall not be null");
synchronized (this) {
mListeners.add(cb);
}
if (mHandler != null) {
Message m = mHandler.obtainMessage(
AUDIOVOLUMEGROUP_EVENT_NEW_LISTENER, 0, 0, cb);
mHandler.sendMessage(m);
}
}
/**
* @param cb the {@link AudioManager.VolumeGroupCallback} to unregister
*/
public void unregisterListener(@NonNull AudioManager.VolumeGroupCallback cb) {
Preconditions.checkNotNull(cb, "volume group callback shall not be null");
synchronized (this) {
mListeners.remove(cb);
}
}
Handler handler() {
return mHandler;
}
@SuppressWarnings("unused")
private static void postEventFromNative(Object moduleRef,
int what, int arg1, int arg2, Object obj) {
AudioVolumeGroupChangeHandler eventHandler =
(AudioVolumeGroupChangeHandler) ((WeakReference) moduleRef).get();
if (eventHandler == null) {
return;
}
if (eventHandler != null) {
Handler handler = eventHandler.handler();
if (handler != null) {
Message m = handler.obtainMessage(what, arg1, arg2, obj);
if (what != AUDIOVOLUMEGROUP_EVENT_NEW_LISTENER) {
handler.removeMessages(what);
}
handler.sendMessage(m);
}
}
}
}

View File

@@ -0,0 +1,18 @@
/* Copyright 2018, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
package android.media.audiopolicy;
parcelable AudioVolumeGroups;

View File

@@ -0,0 +1,135 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media.audiopolicy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.media.AudioSystem;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Iterator;
/**
* @hide
* A class to encapsulate a collection of {@link AudioVolumeGroup}.
*/
@SystemApi
public final class AudioVolumeGroups implements Iterable<AudioVolumeGroup>, Parcelable {
private final ArrayList<AudioVolumeGroup> mAudioVolumeGroupList;
private static final String TAG = "AudioVolumeGroups";
/**
* Volume group value to use when introspection API fails.
*/
public static final int DEFAULT_VOLUME_GROUP = -1;
public AudioVolumeGroups() {
ArrayList<AudioVolumeGroup> avgList = new ArrayList<AudioVolumeGroup>();
int status = native_list_audio_volume_groups(avgList);
if (status != AudioSystem.SUCCESS) {
Log.w(TAG, ": listAudioVolumeGroups failed");
}
mAudioVolumeGroupList = avgList;
}
private AudioVolumeGroups(@NonNull ArrayList<AudioVolumeGroup> audioVolumeGroupList) {
Preconditions.checkNotNull(audioVolumeGroupList, "audioVolumeGroupList must not be null");
mAudioVolumeGroupList = audioVolumeGroupList;
}
/**
* @return number of {@link AudioProductStrategy} objects
*/
public int size() {
return mAudioVolumeGroupList.size();
}
/**
* Returns an {@link Iterator}
*/
@Override
public Iterator<AudioVolumeGroup> iterator() {
return mAudioVolumeGroupList.iterator();
}
@Override
public boolean equals(@NonNull Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AudioVolumeGroups that = (AudioVolumeGroups) o;
return mAudioVolumeGroupList.equals(that.mAudioVolumeGroupList);
}
/**
* @return the matching {@link AudioVolumeGroup} objects with the given id,
* null object if not found.
*/
public @Nullable AudioVolumeGroup getById(int volumeGroupId) {
for (final AudioVolumeGroup avg : this) {
if (avg.getId() == volumeGroupId) {
return avg;
}
}
Log.e(TAG, ": invalid volume group id: " + volumeGroupId + " requested");
return null;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(size());
for (final AudioVolumeGroup volumeGroup : this) {
volumeGroup.writeToParcel(dest, flags);
}
}
private static native int native_list_audio_volume_groups(
ArrayList<AudioVolumeGroup> groups);
public static final Parcelable.Creator<AudioVolumeGroups> CREATOR =
new Parcelable.Creator<AudioVolumeGroups>() {
@Override
public @NonNull AudioVolumeGroups createFromParcel(@NonNull Parcel in) {
Preconditions.checkNotNull(in, "in Parcel must not be null");
ArrayList<AudioVolumeGroup> avgList = new ArrayList<AudioVolumeGroup>();
int size = in.readInt();
for (int index = 0; index < size; index++) {
avgList.add(AudioVolumeGroup.CREATOR.createFromParcel(in));
}
return new AudioVolumeGroups(avgList);
}
@Override
public @NonNull AudioVolumeGroups[] newArray(int size) {
return new AudioVolumeGroups[size];
}
};
}