/* * Copyright (C) 2023 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.car.media; import static android.media.AudioManager.FLAG_FROM_KEY; import static android.media.AudioManager.FLAG_PLAY_SOUND; import static android.media.AudioManager.FLAG_SHOW_UI; import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.util.SparseArray; import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; /** * Class to encapsulate car volume group event information. * * @hide */ @SystemApi public final class CarVolumeGroupEvent implements Parcelable { /** * This event type indicates that the volume group gain index has changed. * The new gain index can be queried through * {@link android.car.media.CarVolumeGroupInfo#getVolumeGainIndex} on the * list of {@link android.car.media.CarVolumeGroupInfo} received here. */ public static final int EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED = 1 << 0; /** * This event type indicates that the volume group minimum gain index has changed. * The new minimum gain index can be queried through * {@link android.car.media.CarVolumeGroupInfo#getMinVolumeGainIndex} on the * list of {@link android.car.media.CarVolumeGroupInfo} received here. */ public static final int EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED = 1 << 1; /** * This event type indicates that the volume group maximum gain index has changed. * The new maximum gain index can be queried through * {@link android.car.media.CarVolumeGroupInfo#getMaxVolumeGainIndex} on the * list of {@link android.car.media.CarVolumeGroupInfo} received here. */ public static final int EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED = 1 << 2; /** * This event type indicates that the volume group mute state changed or that the volume * group is muted by system while trying to unmute it. The new mute state can be queried * through {@link android.car.media.CarVolumeGroupInfo#isMuted} on the * list of {@link android.car.media.CarVolumeGroupInfo} received here. * *

Mute state can be changed from key event, API call, or system. */ public static final int EVENT_TYPE_MUTE_CHANGED = 1 << 3; /** * This event type indicates that the volume group blocked state has changed. * The new state can be queried through * {@link android.car.media.CarVolumeGroupInfo#isBlocked} on the * list of {@link android.car.media.CarVolumeGroupInfo} received here. * *

Note: When the volume group is blocked, the car audio framework may * reject incoming volume and mute change requests from the users. */ public static final int EVENT_TYPE_VOLUME_BLOCKED_CHANGED = 1 << 4; /** * This event type indicates that the volume group attenuation state has changed. * The new state can be queried through * {@link android.car.media.CarVolumeGroupInfo#isAttenuated} on the * list of {@link android.car.media.CarVolumeGroupInfo} received here. * *

Note: The attenuation could be transient or permanent. More * context can be obtained from the included extra information. */ public static final int EVENT_TYPE_ATTENUATION_CHANGED = 1 << 5; /** * This event type indicates that the car audio zone configuration of the volume group has * switched by {@link CarAudioManager#switchAudioZoneToConfig(CarAudioZoneConfigInfo, Executor, * SwitchAudioZoneConfigCallback)}. The new audio attributes can be queried through * {@link android.car.media.CarVolumeGroupInfo#getAudioAttributes()} on the * list of {@link android.car.media.CarVolumeGroupInfo} received here. * *

Note: When the car audio zone configuration is switched, the volume groups * received here are completely new. */ public static final int EVENT_TYPE_ZONE_CONFIGURATION_CHANGED = 1 << 6; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = "EVENT_TYPE", value = { EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED, EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED, EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED, EVENT_TYPE_MUTE_CHANGED, EVENT_TYPE_VOLUME_BLOCKED_CHANGED, EVENT_TYPE_ATTENUATION_CHANGED, EVENT_TYPE_ZONE_CONFIGURATION_CHANGED, }) public @interface EventTypeEnum {} /** * No additional information available */ public static final int EXTRA_INFO_NONE = 100; /** * Indicates volume index changed by Car UI or other user facing apps */ public static final int EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_UI = 101; /** * Indicates volume index changed by keyevents from volume knob, steering wheel keys * etc. Equivalent to {@link android.media.AudioManager#FLAG_FROM_KEY} but specifically * for volume index changes. */ public static final int EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_KEYEVENT = 102; /** * Indicates volume index changed by the audio system (example - external amplifier) * asynchronously. This is typically in response to volume change requests from * car audio framework and needed to maintain sync. */ public static final int EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_AUDIO_SYSTEM = 103; /** * Indicates volume is attenuated due to min/max activation limits set by the OEM. * *

Some examples: *

*/ public static final int EXTRA_INFO_ATTENUATION_ACTIVATION = 110; /** * Indicates volume is attenuated due to thermal throttling (overheating of amplifier * etc). */ public static final int EXTRA_INFO_TRANSIENT_ATTENUATION_THERMAL = 120; /** * Indicates volume is temporarily attenuated due to active ducking (general). */ public static final int EXTRA_INFO_TRANSIENT_ATTENUATION_DUCKED = 121; /** * Indicates volume is temporarily attenuated due to ducking initiated by * projection services. */ public static final int EXTRA_INFO_TRANSIENT_ATTENUATION_PROJECTION = 122; /** * Indicates volume (typically for Media) is temporarily attenuated due to ducking for * navigation usecases. */ public static final int EXTRA_INFO_TRANSIENT_ATTENUATION_NAVIGATION = 123; /** * Indicates volume is temporarily attenuated due to external (example: ADAS) events */ public static final int EXTRA_INFO_TRANSIENT_ATTENUATION_EXTERNAL = 124; /** * Indicates volume group mute toggled by UI */ public static final int EXTRA_INFO_MUTE_TOGGLED_BY_UI = 200; /** * Indicates volume group mute toggled by keyevent (example - volume knob, steering wheel keys * etc). Equivalent to {@link android.media.AudioManager#FLAG_FROM_KEY} but specifically * for mute toggle. */ public static final int EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT = 201; /** * Indicates volume group mute toggled by TCU or due to emergency event * (example: European eCall) in progress */ public static final int EXTRA_INFO_MUTE_TOGGLED_BY_EMERGENCY = 202; /** * Indicates volume group mute toggled by the audio system. This could be due to * its internal states (shutdown, restart, recovery, sw update etc) or other concurrent high * prority audio activity. */ public static final int EXTRA_INFO_MUTE_TOGGLED_BY_AUDIO_SYSTEM = 203; /** * Indicates volume group mute is locked *

Note: such a state may result in rejection of changes by the user */ public static final int EXTRA_INFO_MUTE_LOCKED = 210; /** * Indicates that the client should show an UI for the event(s). Equivalent to * {@link android.media.AudioManager#FLAG_SHOW_UI} */ public static final int EXTRA_INFO_SHOW_UI = 300; /** * Indicates that the client should play sound for the event(s). Equivalent to * {@link android.media.AudioManager#FLAG_PLAY_SOUND} */ public static final int EXTRA_INFO_PLAY_SOUND = 301; private final @EventTypeEnum int mEventTypes; private final @NonNull List mExtraInfos; private final @NonNull List mCarVolumeGroupInfos; private CarVolumeGroupEvent(@NonNull List volumeGroupInfos, @EventTypeEnum int eventTypes, @NonNull List extraInfos) { this.mCarVolumeGroupInfos = Objects.requireNonNull(volumeGroupInfos, "Volume group infos can not be null"); this.mExtraInfos = Objects.requireNonNull(extraInfos, "Extra infos can not be null"); this.mEventTypes = eventTypes; } /** * Returns the list of {@link android.car.media.CarVolumeGroupInfo} that have changed. * * @return list of updated {@link android.car.media.CarVolumeGroupInfo} */ public @NonNull List getCarVolumeGroupInfos() { return List.copyOf(mCarVolumeGroupInfos); } /** * Returns the event types flag * *

Conveys information on "what has changed". {@code EventTypesEnum} * can be used as a flag and supports bitwise operations. * * @return one or more {@code EventTypesEnum}. The returned value can be a combination * of {@link #EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED}, * {@link #EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED}, * {@link #EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED}, * {@link #EVENT_TYPE_MUTE_CHANGED}, * {@link #EVENT_TYPE_VOLUME_BLOCKED_CHANGED}, * {@link #EVENT_TYPE_ATTENUATION_CHANGED} * {@link #EVENT_TYPE_ZONE_CONFIGURATION_CHANGED} */ @EventTypeEnum public int getEventTypes() { return mEventTypes; } /** * Returns list of extra/additional information related to the event types. * *

Conveys information on "why it has changed". This can be used by the client * to provide context to the user. It is expected that OEMs will customize the behavior * as they see fit. Some examples: *

* * @return list of extra info. The returned value can be {@link #EXTRA_INFO_NONE} or * a list of {@link #EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_UI}, * {@link #EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_KEYEVENT}, * {@link #EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_AUDIO_SYSTEM} * {@link #EXTRA_INFO_ATTENUATION_ACTIVATION}, * {@link #EXTRA_INFO_TRANSIENT_ATTENUATION_THERMAL}, * {@link #EXTRA_INFO_TRANSIENT_ATTENUATION_DUCKED}, * {@link #EXTRA_INFO_TRANSIENT_ATTENUATION_PROJECTION}, * {@link #EXTRA_INFO_TRANSIENT_ATTENUATION_NAVIGATION}, * {@link #EXTRA_INFO_TRANSIENT_ATTENUATION_EXTERNAL}, * {@link #EXTRA_INFO_MUTE_TOGGLED_BY_UI}, * {@link #EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT}, * {@link #EXTRA_INFO_MUTE_TOGGLED_BY_EMERGENCY}, * {@link #EXTRA_INFO_MUTE_TOGGLED_BY_AUDIO_SYSTEM}, * {@link #EXTRA_INFO_MUTE_LOCKED}, * {@link #EXTRA_INFO_SHOW_UI}, * {@link #EXTRA_INFO_PLAY_SOUND} */ public @NonNull List getExtraInfos() { return List.copyOf(mExtraInfos); } /** * Converts the list of extra info into flags. * *

Note: Not all values of extra info can be converted into * {@link android.media.AudioManager#Flags}. * * @param extraInfos list of extra info * @return flags One or more flags @link android.media.AudioManager#FLAG_SHOW_UI}, * {@link android.media.AudioManager#FLAG_PLAY_SOUND}, * {@link android.media.AudioManager#FLAG_FROM_KEY} */ public static int convertExtraInfoToFlags(@NonNull List extraInfos) { int flags = 0; if (extraInfos.contains(EXTRA_INFO_SHOW_UI)) { flags |= FLAG_SHOW_UI; } if (extraInfos.contains(EXTRA_INFO_PLAY_SOUND)) { flags |= FLAG_PLAY_SOUND; } if (extraInfos.contains(EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_KEYEVENT) || extraInfos.contains(EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT)) { flags |= FLAG_FROM_KEY; } return flags; } /** * Converts flags into extra info. * *

Note: Not all extra info can be converted into flags. * * @param flags one or more flags. * @param eventTypes one or more event types. * @return list of extra info. The returned value can be {@link #EXTRA_INFO_NONE} or * a list of {@link #EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_KEYEVENT}, * {@link #EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT}, * {@link #EXTRA_INFO_SHOW_UI}, * {@link #EXTRA_INFO_PLAY_SOUND} */ @NonNull public static List convertFlagsToExtraInfo(int flags, int eventTypes) { List extraInfos = new ArrayList<>(); if ((flags & FLAG_SHOW_UI) != 0) { extraInfos.add(EXTRA_INFO_SHOW_UI); } if ((flags & FLAG_PLAY_SOUND) != 0) { extraInfos.add(EXTRA_INFO_PLAY_SOUND); } if ((flags & FLAG_FROM_KEY) != 0) { if ((eventTypes & EVENT_TYPE_MUTE_CHANGED) != 0) { extraInfos.add(EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT); } else if ((eventTypes & EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED) != 0) { extraInfos.add(EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_KEYEVENT); } } if (extraInfos.isEmpty()) { extraInfos.add(EXTRA_INFO_NONE); } return extraInfos; } private static final SparseArray EVENT_TYPE_NAMES = new SparseArray<>(); static { EVENT_TYPE_NAMES.put(EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED, "EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED"); EVENT_TYPE_NAMES.put(EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED, "EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED"); EVENT_TYPE_NAMES.put(EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED, "EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED"); EVENT_TYPE_NAMES.put(EVENT_TYPE_MUTE_CHANGED, "EVENT_TYPE_MUTE_CHANGED"); EVENT_TYPE_NAMES.put(EVENT_TYPE_VOLUME_BLOCKED_CHANGED, "EVENT_TYPE_VOLUME_BLOCKED_CHANGED"); EVENT_TYPE_NAMES.put(EVENT_TYPE_ATTENUATION_CHANGED, "EVENT_TYPE_ATTENUATION_CHANGED"); EVENT_TYPE_NAMES.put(EVENT_TYPE_ZONE_CONFIGURATION_CHANGED, "EVENT_TYPE_ZONE_CONFIGURATION_CHANGED"); } /** * Return {@code EventTypesEnum} as a human-readable string * * @param eventTypes {@code EventTypeEnum} * @return human-readable string */ @NonNull public static String eventTypeToString(@EventTypeEnum int eventTypes) { final StringBuilder sb = new StringBuilder(); for (int i = 0; i < 32; i++) { int eventType = eventTypes & (1 << i); if (eventType != 0) { if (sb.length() > 0) { sb.append('|'); } sb.append(EVENT_TYPE_NAMES.get(eventType, "unknown event type: " + eventType)); } } return sb.toString(); } private static final SparseArray EXTRA_INFO_NAMES = new SparseArray<>(); static { EXTRA_INFO_NAMES.put(EXTRA_INFO_NONE, "EXTRA_INFO_NONE"); EXTRA_INFO_NAMES.put(EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_UI, "EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_UI"); EXTRA_INFO_NAMES.put(EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_KEYEVENT, "EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_KEYEVENT"); EXTRA_INFO_NAMES.put(EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_AUDIO_SYSTEM, "EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_AUDIO_SYSTEM"); EXTRA_INFO_NAMES.put(EXTRA_INFO_ATTENUATION_ACTIVATION, "EXTRA_INFO_ATTENUATION_ACTIVATION"); EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_THERMAL, "EXTRA_INFO_TRANSIENT_ATTENUATION_THERMAL"); EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_DUCKED, "EXTRA_INFO_TRANSIENT_ATTENUATION_DUCKED"); EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_PROJECTION, "EXTRA_INFO_TRANSIENT_ATTENUATION_PROJECTION"); EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_NAVIGATION, "EXTRA_INFO_TRANSIENT_ATTENUATION_NAVIGATION"); EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_EXTERNAL, "EXTRA_INFO_TRANSIENT_ATTENUATION_EXTERNAL"); EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_TOGGLED_BY_UI, "EXTRA_INFO_MUTE_TOGGLED_BY_UI"); EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT, "EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT"); EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_TOGGLED_BY_EMERGENCY, "EXTRA_INFO_MUTE_TOGGLED_BY_EMERGENCY"); EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_TOGGLED_BY_AUDIO_SYSTEM, "EXTRA_INFO_MUTE_TOGGLED_BY_AUDIO_SYSTEM"); EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_LOCKED, "EXTRA_INFO_MUTE_LOCKED"); EXTRA_INFO_NAMES.put(EXTRA_INFO_SHOW_UI, "EXTRA_INFO_SHOW_UI"); EXTRA_INFO_NAMES.put(EXTRA_INFO_PLAY_SOUND, "EXTRA_INFO_PLAY_SOUND"); } /** * Returns list of extra-infos as human-readable string * * @param extraInfos list of extra-info * @return human-readable string */ @NonNull public static String extraInfosToString(@NonNull List extraInfos) { final StringBuilder sb = new StringBuilder(); for (int extraInfo : extraInfos) { if (sb.length() > 0) { sb.append(','); } sb.append(EXTRA_INFO_NAMES.get(extraInfo, "unknown extra info: " + extraInfo)); } return sb.toString(); } @Override public String toString() { return new StringBuilder().append("CarVolumeGroupEvent { mCarVolumeGroupInfos = ") .append(mCarVolumeGroupInfos) .append(", mEventTypes = ").append(eventTypeToString(mEventTypes)) .append(", mExtraInfos = ").append(extraInfosToString(mExtraInfos)) .append(" }").toString(); } @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeParcelableList(mCarVolumeGroupInfos, flags); dest.writeInt(mEventTypes); dest.writeList(mExtraInfos); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof CarVolumeGroupEvent)) { return false; } CarVolumeGroupEvent rhs = (CarVolumeGroupEvent) o; return mCarVolumeGroupInfos.equals(rhs.mCarVolumeGroupInfos) && (mEventTypes == rhs.mEventTypes) && mExtraInfos.equals(rhs.mExtraInfos); } /** * Creates volume group event from parcel * * @hide */ @VisibleForTesting public CarVolumeGroupEvent(Parcel in) { List volumeGroupInfos = new ArrayList<>(); in.readParcelableList(volumeGroupInfos, CarVolumeGroupInfo.class.getClassLoader(), CarVolumeGroupInfo.class); int eventTypes = in.readInt(); List extraInfos = new ArrayList<>(); in.readList(extraInfos, Integer.class.getClassLoader(), java.lang.Integer.class); this.mCarVolumeGroupInfos = volumeGroupInfos; this.mEventTypes = eventTypes; this.mExtraInfos = extraInfos; } @NonNull public static final Creator CREATOR = new Creator<>() { @Override @NonNull public CarVolumeGroupEvent createFromParcel(@NonNull Parcel in) { return new CarVolumeGroupEvent(in); } @Override @NonNull public CarVolumeGroupEvent[] newArray(int size) { return new CarVolumeGroupEvent[size]; } }; @Override public int hashCode() { return Objects.hash(mCarVolumeGroupInfos, mEventTypes, mExtraInfos); } /** * A builder for {@link CarVolumeGroupEvent} */ @SuppressWarnings("WeakerAccess") public static final class Builder { private static final long IS_USED_FIELD_SET = 0x01; private @NonNull List mCarVolumeGroupInfos; private @EventTypeEnum int mEventTypes; private @NonNull List mExtraInfos; private long mBuilderFieldsSet; public Builder(@NonNull List volumeGroupInfos, @EventTypeEnum int eventTypes) { Preconditions.checkArgument(volumeGroupInfos != null, "Volume group infos can not be null"); mCarVolumeGroupInfos = volumeGroupInfos; mEventTypes = eventTypes; } public Builder(@NonNull List volumeGroupInfos, @EventTypeEnum int eventTypes, @NonNull List extraInfos) { Preconditions.checkArgument(volumeGroupInfos != null, "Volume group infos can not be null"); Preconditions.checkArgument(extraInfos != null, "Extra infos can not be null"); // TODO (b/261647905) validate extra infos, make sure EXTRA_INFO_NONE // is not part of list mCarVolumeGroupInfos = volumeGroupInfos; mEventTypes = eventTypes; mExtraInfos = extraInfos; } /** @see CarVolumeGroupEvent#getCarVolumeGroupInfos() **/ @NonNull public Builder addCarVolumeGroupInfo(@NonNull CarVolumeGroupInfo volumeGroupInfo) { Preconditions.checkArgument(volumeGroupInfo != null, "Volume group info can not be null"); mCarVolumeGroupInfos.add(volumeGroupInfo); return this; } /** @see CarVolumeGroupEvent#getEventTypes() **/ @NonNull public Builder addEventType(@EventTypeEnum int eventType) { mEventTypes |= eventType; return this; } /** @see CarVolumeGroupEvent#getExtraInfos **/ @NonNull public Builder setExtraInfos(@NonNull List extraInfos) { Preconditions.checkArgument(extraInfos != null, "Extra infos can not be null"); mExtraInfos = extraInfos; return this; } /** @see #setExtraInfos(List) **/ @NonNull public Builder addExtraInfo(int extraInfo) { if (mExtraInfos == null) { setExtraInfos(new ArrayList<>()); } // TODO (b/261647905) validate extra infos, make sure EXTRA_INFO_NONE // is not part of list if (!mExtraInfos.contains(extraInfo)) { mExtraInfos.add(extraInfo); } return this; } /** Builds the instance. This builder should not be touched after calling this! */ @NonNull public CarVolumeGroupEvent build() { checkNotUsed(); mBuilderFieldsSet |= IS_USED_FIELD_SET; // Mark builder used // mark as EXTRA_INFO_NONE if none is available if (mExtraInfos == null) { mExtraInfos = List.of(EXTRA_INFO_NONE); } return new CarVolumeGroupEvent(mCarVolumeGroupInfos, mEventTypes, mExtraInfos); } private void checkNotUsed() throws IllegalStateException { if ((mBuilderFieldsSet & IS_USED_FIELD_SET) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } } } }