1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.car.media;
18 
19 import static android.media.AudioManager.FLAG_FROM_KEY;
20 import static android.media.AudioManager.FLAG_PLAY_SOUND;
21 import static android.media.AudioManager.FLAG_SHOW_UI;
22 
23 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
24 
25 import android.annotation.IntDef;
26 import android.annotation.NonNull;
27 import android.annotation.SystemApi;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.util.SparseArray;
31 
32 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.internal.util.Preconditions;
35 
36 import java.lang.annotation.Retention;
37 import java.lang.annotation.RetentionPolicy;
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.Objects;
41 import java.util.concurrent.Executor;
42 
43 /**
44  * Class to encapsulate car volume group event information.
45  *
46  * @hide
47  */
48 @SystemApi
49 public final class CarVolumeGroupEvent implements Parcelable {
50 
51     /**
52      * This event type indicates that the volume group gain index has changed.
53      * The new gain index can be queried through
54      * {@link android.car.media.CarVolumeGroupInfo#getVolumeGainIndex} on the
55      * list of {@link android.car.media.CarVolumeGroupInfo} received here.
56      */
57     public static final int EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED = 1 << 0;
58 
59     /**
60      * This event type indicates that the volume group minimum gain index has changed.
61      * The new minimum gain index can be queried through
62      * {@link android.car.media.CarVolumeGroupInfo#getMinVolumeGainIndex} on the
63      * list of {@link android.car.media.CarVolumeGroupInfo} received here.
64      */
65     public static final int EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED = 1 << 1;
66 
67     /**
68      * This event type indicates that the volume group maximum gain index has changed.
69      * The new maximum gain index can be queried through
70      * {@link android.car.media.CarVolumeGroupInfo#getMaxVolumeGainIndex} on the
71      * list of {@link android.car.media.CarVolumeGroupInfo} received here.
72      */
73     public static final int EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED = 1 << 2;
74 
75     /**
76      * This event type indicates that the volume group mute state changed or that the volume
77      * group is muted by system while trying to unmute it. The new mute state can be queried
78      * through {@link android.car.media.CarVolumeGroupInfo#isMuted} on the
79      * list of {@link android.car.media.CarVolumeGroupInfo} received here.
80      *
81      * <p>Mute state can be changed from key event, API call, or system.
82      */
83     public static final int EVENT_TYPE_MUTE_CHANGED = 1 << 3;
84 
85     /**
86      * This event type indicates that the volume group blocked state has changed.
87      * The new state can be queried through
88      * {@link android.car.media.CarVolumeGroupInfo#isBlocked} on the
89      * list of {@link android.car.media.CarVolumeGroupInfo} received here.
90      *
91      * <p><b> Note: </b> When the volume group is blocked, the car audio framework may
92      * reject incoming volume and mute change requests from the users.
93      */
94     public static final int EVENT_TYPE_VOLUME_BLOCKED_CHANGED = 1 << 4;
95 
96     /**
97      * This event type indicates that the volume group attenuation state has changed.
98      * The new state can be queried through
99      * {@link android.car.media.CarVolumeGroupInfo#isAttenuated} on the
100      * list of {@link android.car.media.CarVolumeGroupInfo} received here.
101      *
102      * <p> <b> Note: </b> The attenuation could be transient or permanent. More
103      * context can be obtained from the included extra information.
104      */
105     public static final int EVENT_TYPE_ATTENUATION_CHANGED = 1 << 5;
106 
107     /**
108      * This event type indicates that the car audio zone configuration of the volume group has
109      * switched by {@link CarAudioManager#switchAudioZoneToConfig(CarAudioZoneConfigInfo, Executor,
110      * SwitchAudioZoneConfigCallback)}. The new audio attributes can be queried through
111      * {@link android.car.media.CarVolumeGroupInfo#getAudioAttributes()} on the
112      * list of {@link android.car.media.CarVolumeGroupInfo} received here.
113      *
114      * <p><b> Note: </b> When the car audio zone configuration is switched, the volume groups
115      * received here are completely new.
116      */
117     public static final int EVENT_TYPE_ZONE_CONFIGURATION_CHANGED = 1 << 6;
118 
119     /** @hide */
120     @Retention(RetentionPolicy.SOURCE)
121     @IntDef(flag = true, prefix = "EVENT_TYPE", value = {
122             EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED,
123             EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED,
124             EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED,
125             EVENT_TYPE_MUTE_CHANGED,
126             EVENT_TYPE_VOLUME_BLOCKED_CHANGED,
127             EVENT_TYPE_ATTENUATION_CHANGED,
128             EVENT_TYPE_ZONE_CONFIGURATION_CHANGED,
129     })
130     public @interface EventTypeEnum {}
131 
132     /**
133      * No additional information available
134      */
135     public static final int EXTRA_INFO_NONE = 100;
136 
137     /**
138      * Indicates volume index changed by Car UI or other user facing apps
139      */
140     public static final int EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_UI = 101;
141 
142     /**
143      * Indicates volume index changed by keyevents from volume knob, steering wheel keys
144      * etc. Equivalent to {@link android.media.AudioManager#FLAG_FROM_KEY} but specifically
145      * for volume index changes.
146      */
147     public static final int EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_KEYEVENT = 102;
148 
149     /**
150      * Indicates volume index changed by the audio system (example - external amplifier)
151      * asynchronously. This is typically in response to volume change requests from
152      * car audio framework and needed to maintain sync.
153      */
154     public static final int EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_AUDIO_SYSTEM = 103;
155 
156     /**
157      * Indicates volume is attenuated due to min/max activation limits set by the OEM.
158      *
159      * <p>Some examples:
160      * <ul>
161      *     <li>Current media volume level is higher than allowed maximum activation volume</li>
162      *     <li>Current call volume level is lower than expected minimum activation volume</li>
163      * </ul>
164      */
165     public static final int EXTRA_INFO_ATTENUATION_ACTIVATION = 110;
166 
167     /**
168      * Indicates volume is attenuated due to thermal throttling (overheating of amplifier
169      * etc).
170      */
171     public static final int EXTRA_INFO_TRANSIENT_ATTENUATION_THERMAL = 120;
172 
173     /**
174      * Indicates volume is temporarily attenuated due to active ducking (general).
175      */
176     public static final int EXTRA_INFO_TRANSIENT_ATTENUATION_DUCKED = 121;
177 
178     /**
179      * Indicates volume is temporarily attenuated due to ducking initiated by
180      * projection services.
181      */
182     public static final int EXTRA_INFO_TRANSIENT_ATTENUATION_PROJECTION = 122;
183 
184     /**
185      * Indicates volume (typically for Media) is temporarily attenuated due to ducking for
186      * navigation usecases.
187      */
188     public static final int EXTRA_INFO_TRANSIENT_ATTENUATION_NAVIGATION = 123;
189 
190     /**
191      * Indicates volume is temporarily attenuated due to external (example: ADAS) events
192      */
193     public static final int EXTRA_INFO_TRANSIENT_ATTENUATION_EXTERNAL = 124;
194 
195     /**
196      * Indicates volume group mute toggled by UI
197      */
198     public static final int EXTRA_INFO_MUTE_TOGGLED_BY_UI = 200;
199 
200     /**
201      * Indicates volume group mute toggled by keyevent (example - volume knob, steering wheel keys
202      * etc). Equivalent to {@link android.media.AudioManager#FLAG_FROM_KEY} but specifically
203      * for mute toggle.
204      */
205     public static final int EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT = 201;
206 
207     /**
208      * Indicates volume group mute toggled by TCU or due to emergency event
209      * (example: European eCall) in progress
210      */
211     public static final int EXTRA_INFO_MUTE_TOGGLED_BY_EMERGENCY = 202;
212 
213     /**
214      * Indicates volume group mute toggled by the audio system. This could be due to
215      * its internal states (shutdown, restart, recovery, sw update etc) or other concurrent high
216      * prority audio activity.
217      */
218     public static final int EXTRA_INFO_MUTE_TOGGLED_BY_AUDIO_SYSTEM = 203;
219 
220     /**
221      * Indicates volume group mute is locked
222      * <p> <b>Note:</b> such a state may result in rejection of changes by the user
223      */
224     public static final int EXTRA_INFO_MUTE_LOCKED = 210;
225 
226     /**
227      * Indicates that the client should show an UI for the event(s). Equivalent to
228      * {@link android.media.AudioManager#FLAG_SHOW_UI}
229      */
230     public static final int EXTRA_INFO_SHOW_UI = 300;
231 
232     /**
233      * Indicates that the client should play sound for the event(s). Equivalent to
234      * {@link android.media.AudioManager#FLAG_PLAY_SOUND}
235      */
236     public static final int EXTRA_INFO_PLAY_SOUND = 301;
237 
238     private final @EventTypeEnum int mEventTypes;
239     private final @NonNull List<Integer> mExtraInfos;
240     private final @NonNull List<CarVolumeGroupInfo> mCarVolumeGroupInfos;
241 
CarVolumeGroupEvent(@onNull List<CarVolumeGroupInfo> volumeGroupInfos, @EventTypeEnum int eventTypes, @NonNull List<Integer> extraInfos)242     private CarVolumeGroupEvent(@NonNull List<CarVolumeGroupInfo> volumeGroupInfos,
243                                 @EventTypeEnum int eventTypes,
244                                 @NonNull List<Integer> extraInfos) {
245         this.mCarVolumeGroupInfos = Objects.requireNonNull(volumeGroupInfos,
246                 "Volume group infos can not be null");
247         this.mExtraInfos = Objects.requireNonNull(extraInfos, "Extra infos can not be null");
248         this.mEventTypes = eventTypes;
249     }
250 
251     /**
252      * Returns the list of {@link android.car.media.CarVolumeGroupInfo} that have changed.
253      *
254      * @return list of updated {@link android.car.media.CarVolumeGroupInfo}
255      */
getCarVolumeGroupInfos()256     public @NonNull List<CarVolumeGroupInfo> getCarVolumeGroupInfos() {
257         return List.copyOf(mCarVolumeGroupInfos);
258     }
259 
260     /**
261      * Returns the event types flag
262      *
263      * <p>Conveys information on "what has changed". {@code EventTypesEnum}
264      * can be used as a flag and supports bitwise operations.
265      *
266      * @return one or more {@code EventTypesEnum}. The returned value can be a combination
267      *         of {@link #EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED},
268      *         {@link #EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED},
269      *         {@link #EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED},
270      *         {@link #EVENT_TYPE_MUTE_CHANGED},
271      *         {@link #EVENT_TYPE_VOLUME_BLOCKED_CHANGED},
272      *         {@link #EVENT_TYPE_ATTENUATION_CHANGED}
273      *         {@link #EVENT_TYPE_ZONE_CONFIGURATION_CHANGED}
274      */
275     @EventTypeEnum
getEventTypes()276     public int getEventTypes() {
277         return mEventTypes;
278     }
279 
280     /**
281      * Returns list of extra/additional information related to the event types.
282      *
283      * <p>Conveys information on "why it has changed". This can be used by the client
284      * to provide context to the user. It is expected that OEMs will customize the behavior
285      * as they see fit. Some examples:
286      * <ul>
287      *     <li>On {@link #EXTRA_INFO_TRANSIENT_ATTENUATION_THERMAL} the client may notify
288      *     the user that the volume is attenuated due to overheating of audio amplifier.</li>
289      *     <li>On {@link #EXTRA_INFO_TRANSIENT_ATTENUATION_NAVIGATION} the client may initially
290      *     gray out the volume bar with a toast message to inform the user the volume group is
291      *     currently ducked.</li>
292      *     <li>On {@link #EXTRA_INFO_MUTE_TOGGLED_BY_EMERGENCY} the client may notify the user
293      *     that the volume group is muted due to concurrent emergency audio activity.</li>
294      * </ul>
295      *
296      * @return list of extra info. The returned value can be {@link #EXTRA_INFO_NONE} or
297      *         a list of {@link #EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_UI},
298      *         {@link #EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_KEYEVENT},
299      *         {@link #EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_AUDIO_SYSTEM}
300      *         {@link #EXTRA_INFO_ATTENUATION_ACTIVATION},
301      *         {@link #EXTRA_INFO_TRANSIENT_ATTENUATION_THERMAL},
302      *         {@link #EXTRA_INFO_TRANSIENT_ATTENUATION_DUCKED},
303      *         {@link #EXTRA_INFO_TRANSIENT_ATTENUATION_PROJECTION},
304      *         {@link #EXTRA_INFO_TRANSIENT_ATTENUATION_NAVIGATION},
305      *         {@link #EXTRA_INFO_TRANSIENT_ATTENUATION_EXTERNAL},
306      *         {@link #EXTRA_INFO_MUTE_TOGGLED_BY_UI},
307      *         {@link #EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT},
308      *         {@link #EXTRA_INFO_MUTE_TOGGLED_BY_EMERGENCY},
309      *         {@link #EXTRA_INFO_MUTE_TOGGLED_BY_AUDIO_SYSTEM},
310      *         {@link #EXTRA_INFO_MUTE_LOCKED},
311      *         {@link #EXTRA_INFO_SHOW_UI},
312      *         {@link #EXTRA_INFO_PLAY_SOUND}
313      */
getExtraInfos()314     public @NonNull List<Integer> getExtraInfos() {
315         return List.copyOf(mExtraInfos);
316     }
317 
318     /**
319      * Converts the list of extra info into flags.
320      *
321      * <p><b>Note:</b> Not all values of extra info can be converted into
322      * {@link android.media.AudioManager#Flags}.
323      *
324      * @param extraInfos  list of extra info
325      * @return flags One or more flags @link android.media.AudioManager#FLAG_SHOW_UI},
326      *         {@link android.media.AudioManager#FLAG_PLAY_SOUND},
327      *         {@link android.media.AudioManager#FLAG_FROM_KEY}
328      */
convertExtraInfoToFlags(@onNull List<Integer> extraInfos)329     public static int convertExtraInfoToFlags(@NonNull List<Integer> extraInfos) {
330         int flags = 0;
331         if (extraInfos.contains(EXTRA_INFO_SHOW_UI)) {
332             flags |= FLAG_SHOW_UI;
333         }
334         if (extraInfos.contains(EXTRA_INFO_PLAY_SOUND)) {
335             flags |= FLAG_PLAY_SOUND;
336         }
337         if (extraInfos.contains(EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_KEYEVENT)
338                 || extraInfos.contains(EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT)) {
339             flags |= FLAG_FROM_KEY;
340         }
341         return flags;
342     }
343 
344     /**
345      * Converts flags into extra info.
346      *
347      * <p><b>Note:</b> Not all extra info can be converted into flags.
348      *
349      * @param flags one or more flags.
350      * @param eventTypes one or more event types.
351      * @return list of extra info. The returned value can be {@link #EXTRA_INFO_NONE} or
352      *         a list of {@link #EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_KEYEVENT},
353      *         {@link #EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT},
354      *         {@link #EXTRA_INFO_SHOW_UI},
355      *         {@link #EXTRA_INFO_PLAY_SOUND}
356      */
357     @NonNull
convertFlagsToExtraInfo(int flags, int eventTypes)358     public static List<Integer> convertFlagsToExtraInfo(int flags, int eventTypes) {
359         List<Integer> extraInfos = new ArrayList<>();
360 
361         if ((flags & FLAG_SHOW_UI) != 0) {
362             extraInfos.add(EXTRA_INFO_SHOW_UI);
363         }
364 
365         if ((flags & FLAG_PLAY_SOUND) != 0) {
366             extraInfos.add(EXTRA_INFO_PLAY_SOUND);
367         }
368 
369         if ((flags & FLAG_FROM_KEY) != 0) {
370             if ((eventTypes & EVENT_TYPE_MUTE_CHANGED) != 0) {
371                 extraInfos.add(EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT);
372             } else if ((eventTypes & EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED) != 0) {
373                 extraInfos.add(EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_KEYEVENT);
374             }
375         }
376 
377         if (extraInfos.isEmpty()) {
378             extraInfos.add(EXTRA_INFO_NONE);
379         }
380 
381         return extraInfos;
382     }
383 
384     private static final SparseArray<String> EVENT_TYPE_NAMES = new SparseArray<>();
385 
386     static {
EVENT_TYPE_NAMES.put(EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED, "EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED")387         EVENT_TYPE_NAMES.put(EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED,
388                 "EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED");
EVENT_TYPE_NAMES.put(EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED, "EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED")389         EVENT_TYPE_NAMES.put(EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED,
390                 "EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED");
EVENT_TYPE_NAMES.put(EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED, "EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED")391         EVENT_TYPE_NAMES.put(EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED,
392                 "EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED");
EVENT_TYPE_NAMES.put(EVENT_TYPE_MUTE_CHANGED, "EVENT_TYPE_MUTE_CHANGED")393         EVENT_TYPE_NAMES.put(EVENT_TYPE_MUTE_CHANGED,
394                 "EVENT_TYPE_MUTE_CHANGED");
EVENT_TYPE_NAMES.put(EVENT_TYPE_VOLUME_BLOCKED_CHANGED, "EVENT_TYPE_VOLUME_BLOCKED_CHANGED")395         EVENT_TYPE_NAMES.put(EVENT_TYPE_VOLUME_BLOCKED_CHANGED,
396                 "EVENT_TYPE_VOLUME_BLOCKED_CHANGED");
EVENT_TYPE_NAMES.put(EVENT_TYPE_ATTENUATION_CHANGED, "EVENT_TYPE_ATTENUATION_CHANGED")397         EVENT_TYPE_NAMES.put(EVENT_TYPE_ATTENUATION_CHANGED,
398                 "EVENT_TYPE_ATTENUATION_CHANGED");
EVENT_TYPE_NAMES.put(EVENT_TYPE_ZONE_CONFIGURATION_CHANGED, "EVENT_TYPE_ZONE_CONFIGURATION_CHANGED")399         EVENT_TYPE_NAMES.put(EVENT_TYPE_ZONE_CONFIGURATION_CHANGED,
400                 "EVENT_TYPE_ZONE_CONFIGURATION_CHANGED");
401     }
402 
403     /**
404      *  Return {@code EventTypesEnum} as a human-readable string
405      *
406      * @param eventTypes {@code EventTypeEnum}
407      * @return human-readable string
408      */
409     @NonNull
eventTypeToString(@ventTypeEnum int eventTypes)410     public static String eventTypeToString(@EventTypeEnum int eventTypes) {
411         final StringBuilder sb = new StringBuilder();
412         for (int i = 0; i < 32; i++) {
413             int eventType = eventTypes & (1 << i);
414             if (eventType != 0) {
415                 if (sb.length() > 0) {
416                     sb.append('|');
417                 }
418                 sb.append(EVENT_TYPE_NAMES.get(eventType,
419                         "unknown event type: " + eventType));
420             }
421         }
422         return sb.toString();
423     }
424 
425     private static final SparseArray<String> EXTRA_INFO_NAMES = new SparseArray<>();
426 
427     static {
EXTRA_INFO_NAMES.put(EXTRA_INFO_NONE, "EXTRA_INFO_NONE")428         EXTRA_INFO_NAMES.put(EXTRA_INFO_NONE,
429                 "EXTRA_INFO_NONE");
EXTRA_INFO_NAMES.put(EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_UI, "EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_UI")430         EXTRA_INFO_NAMES.put(EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_UI,
431                 "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")432         EXTRA_INFO_NAMES.put(EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_KEYEVENT,
433                 "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")434         EXTRA_INFO_NAMES.put(EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_AUDIO_SYSTEM,
435                 "EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_AUDIO_SYSTEM");
EXTRA_INFO_NAMES.put(EXTRA_INFO_ATTENUATION_ACTIVATION, "EXTRA_INFO_ATTENUATION_ACTIVATION")436         EXTRA_INFO_NAMES.put(EXTRA_INFO_ATTENUATION_ACTIVATION,
437                 "EXTRA_INFO_ATTENUATION_ACTIVATION");
EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_THERMAL, "EXTRA_INFO_TRANSIENT_ATTENUATION_THERMAL")438         EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_THERMAL,
439                 "EXTRA_INFO_TRANSIENT_ATTENUATION_THERMAL");
EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_DUCKED, "EXTRA_INFO_TRANSIENT_ATTENUATION_DUCKED")440         EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_DUCKED,
441                 "EXTRA_INFO_TRANSIENT_ATTENUATION_DUCKED");
EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_PROJECTION, "EXTRA_INFO_TRANSIENT_ATTENUATION_PROJECTION")442         EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_PROJECTION,
443                 "EXTRA_INFO_TRANSIENT_ATTENUATION_PROJECTION");
EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_NAVIGATION, "EXTRA_INFO_TRANSIENT_ATTENUATION_NAVIGATION")444         EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_NAVIGATION,
445                 "EXTRA_INFO_TRANSIENT_ATTENUATION_NAVIGATION");
EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_EXTERNAL, "EXTRA_INFO_TRANSIENT_ATTENUATION_EXTERNAL")446         EXTRA_INFO_NAMES.put(EXTRA_INFO_TRANSIENT_ATTENUATION_EXTERNAL,
447                 "EXTRA_INFO_TRANSIENT_ATTENUATION_EXTERNAL");
EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_TOGGLED_BY_UI, "EXTRA_INFO_MUTE_TOGGLED_BY_UI")448         EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_TOGGLED_BY_UI,
449                 "EXTRA_INFO_MUTE_TOGGLED_BY_UI");
EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT, "EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT")450         EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT,
451                 "EXTRA_INFO_MUTE_TOGGLED_BY_KEYEVENT");
EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_TOGGLED_BY_EMERGENCY, "EXTRA_INFO_MUTE_TOGGLED_BY_EMERGENCY")452         EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_TOGGLED_BY_EMERGENCY,
453                 "EXTRA_INFO_MUTE_TOGGLED_BY_EMERGENCY");
EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_TOGGLED_BY_AUDIO_SYSTEM, "EXTRA_INFO_MUTE_TOGGLED_BY_AUDIO_SYSTEM")454         EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_TOGGLED_BY_AUDIO_SYSTEM,
455                 "EXTRA_INFO_MUTE_TOGGLED_BY_AUDIO_SYSTEM");
EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_LOCKED, "EXTRA_INFO_MUTE_LOCKED")456         EXTRA_INFO_NAMES.put(EXTRA_INFO_MUTE_LOCKED,
457                 "EXTRA_INFO_MUTE_LOCKED");
EXTRA_INFO_NAMES.put(EXTRA_INFO_SHOW_UI, "EXTRA_INFO_SHOW_UI")458         EXTRA_INFO_NAMES.put(EXTRA_INFO_SHOW_UI,
459                 "EXTRA_INFO_SHOW_UI");
EXTRA_INFO_NAMES.put(EXTRA_INFO_PLAY_SOUND, "EXTRA_INFO_PLAY_SOUND")460         EXTRA_INFO_NAMES.put(EXTRA_INFO_PLAY_SOUND,
461                 "EXTRA_INFO_PLAY_SOUND");
462     }
463 
464     /**
465      * Returns list of extra-infos as human-readable string
466      *
467      * @param extraInfos list of extra-info
468      * @return human-readable string
469      */
470     @NonNull
extraInfosToString(@onNull List<Integer> extraInfos)471     public static String extraInfosToString(@NonNull List<Integer> extraInfos) {
472         final StringBuilder sb = new StringBuilder();
473         for (int extraInfo : extraInfos) {
474             if (sb.length() > 0) {
475                 sb.append(',');
476             }
477             sb.append(EXTRA_INFO_NAMES.get(extraInfo,
478                     "unknown extra info: " + extraInfo));
479         }
480         return sb.toString();
481     }
482 
483     @Override
toString()484     public String toString() {
485         return new StringBuilder().append("CarVolumeGroupEvent { mCarVolumeGroupInfos = ")
486                 .append(mCarVolumeGroupInfos)
487                 .append(", mEventTypes = ").append(eventTypeToString(mEventTypes))
488                 .append(", mExtraInfos = ").append(extraInfosToString(mExtraInfos))
489                 .append(" }").toString();
490     }
491 
492     @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
493     @Override
describeContents()494     public int describeContents() {
495         return 0;
496     }
497 
498     @Override
writeToParcel(@onNull Parcel dest, int flags)499     public void writeToParcel(@NonNull Parcel dest, int flags) {
500         dest.writeParcelableList(mCarVolumeGroupInfos, flags);
501         dest.writeInt(mEventTypes);
502         dest.writeList(mExtraInfos);
503     }
504 
505     @Override
equals(Object o)506     public boolean equals(Object o) {
507         if (this == o) {
508             return true;
509         }
510 
511         if (!(o instanceof CarVolumeGroupEvent)) {
512             return false;
513         }
514 
515         CarVolumeGroupEvent rhs = (CarVolumeGroupEvent) o;
516 
517         return mCarVolumeGroupInfos.equals(rhs.mCarVolumeGroupInfos)
518                 && (mEventTypes == rhs.mEventTypes)
519                 && mExtraInfos.equals(rhs.mExtraInfos);
520     }
521 
522     /**
523      * Creates volume group event from parcel
524      *
525      * @hide
526      */
527     @VisibleForTesting
CarVolumeGroupEvent(Parcel in)528     public CarVolumeGroupEvent(Parcel in) {
529         List<CarVolumeGroupInfo> volumeGroupInfos = new ArrayList<>();
530         in.readParcelableList(volumeGroupInfos, CarVolumeGroupInfo.class.getClassLoader(),
531                 CarVolumeGroupInfo.class);
532         int eventTypes = in.readInt();
533         List<Integer> extraInfos = new ArrayList<>();
534         in.readList(extraInfos, Integer.class.getClassLoader(), java.lang.Integer.class);
535         this.mCarVolumeGroupInfos = volumeGroupInfos;
536         this.mEventTypes = eventTypes;
537         this.mExtraInfos = extraInfos;
538     }
539 
540     @NonNull
541     public static final Creator<CarVolumeGroupEvent> CREATOR = new Creator<>() {
542         @Override
543         @NonNull
544         public CarVolumeGroupEvent createFromParcel(@NonNull Parcel in) {
545             return new CarVolumeGroupEvent(in);
546         }
547 
548         @Override
549         @NonNull
550         public CarVolumeGroupEvent[] newArray(int size) {
551             return new CarVolumeGroupEvent[size];
552         }
553     };
554 
555     @Override
hashCode()556     public int hashCode() {
557         return Objects.hash(mCarVolumeGroupInfos, mEventTypes, mExtraInfos);
558     }
559 
560     /**
561      * A builder for {@link CarVolumeGroupEvent}
562      */
563     @SuppressWarnings("WeakerAccess")
564     public static final class Builder {
565         private static final long IS_USED_FIELD_SET = 0x01;
566         private @NonNull List<CarVolumeGroupInfo> mCarVolumeGroupInfos;
567         private @EventTypeEnum int mEventTypes;
568         private @NonNull List<Integer> mExtraInfos;
569 
570         private long mBuilderFieldsSet;
571 
Builder(@onNull List<CarVolumeGroupInfo> volumeGroupInfos, @EventTypeEnum int eventTypes)572         public Builder(@NonNull List<CarVolumeGroupInfo> volumeGroupInfos,
573                        @EventTypeEnum int eventTypes) {
574             Preconditions.checkArgument(volumeGroupInfos != null,
575                     "Volume group infos can not be null");
576             mCarVolumeGroupInfos = volumeGroupInfos;
577             mEventTypes = eventTypes;
578         }
579 
Builder(@onNull List<CarVolumeGroupInfo> volumeGroupInfos, @EventTypeEnum int eventTypes, @NonNull List<Integer> extraInfos)580         public Builder(@NonNull List<CarVolumeGroupInfo> volumeGroupInfos,
581                        @EventTypeEnum int eventTypes,
582                        @NonNull List<Integer> extraInfos) {
583             Preconditions.checkArgument(volumeGroupInfos != null,
584                     "Volume group infos can not be null");
585             Preconditions.checkArgument(extraInfos != null, "Extra infos can not be null");
586             // TODO (b/261647905) validate extra infos, make sure EXTRA_INFO_NONE
587             //  is not part of list
588             mCarVolumeGroupInfos = volumeGroupInfos;
589             mEventTypes = eventTypes;
590             mExtraInfos = extraInfos;
591         }
592 
593         /** @see CarVolumeGroupEvent#getCarVolumeGroupInfos() **/
594         @NonNull
addCarVolumeGroupInfo(@onNull CarVolumeGroupInfo volumeGroupInfo)595         public Builder addCarVolumeGroupInfo(@NonNull CarVolumeGroupInfo volumeGroupInfo) {
596             Preconditions.checkArgument(volumeGroupInfo != null,
597                     "Volume group info can not be null");
598             mCarVolumeGroupInfos.add(volumeGroupInfo);
599             return this;
600         }
601 
602         /** @see CarVolumeGroupEvent#getEventTypes()  **/
603         @NonNull
addEventType(@ventTypeEnum int eventType)604         public Builder addEventType(@EventTypeEnum int eventType) {
605             mEventTypes |= eventType;
606             return this;
607         }
608 
609         /** @see CarVolumeGroupEvent#getExtraInfos **/
610         @NonNull
setExtraInfos(@onNull List<Integer> extraInfos)611         public Builder setExtraInfos(@NonNull List<Integer> extraInfos) {
612             Preconditions.checkArgument(extraInfos != null, "Extra infos can not be null");
613             mExtraInfos = extraInfos;
614             return this;
615         }
616 
617         /** @see #setExtraInfos(List)  **/
618         @NonNull
addExtraInfo(int extraInfo)619         public Builder addExtraInfo(int extraInfo) {
620             if (mExtraInfos == null) {
621                 setExtraInfos(new ArrayList<>());
622             }
623             // TODO (b/261647905) validate extra infos, make sure EXTRA_INFO_NONE
624             //  is not part of list
625             if (!mExtraInfos.contains(extraInfo)) {
626                 mExtraInfos.add(extraInfo);
627             }
628             return this;
629         }
630 
631         /** Builds the instance. This builder should not be touched after calling this! */
632         @NonNull
build()633         public CarVolumeGroupEvent build() {
634             checkNotUsed();
635             mBuilderFieldsSet |= IS_USED_FIELD_SET; // Mark builder used
636             // mark as EXTRA_INFO_NONE if none is available
637             if (mExtraInfos == null) {
638                 mExtraInfos = List.of(EXTRA_INFO_NONE);
639             }
640 
641             return new CarVolumeGroupEvent(mCarVolumeGroupInfos, mEventTypes, mExtraInfos);
642         }
643 
checkNotUsed()644         private void checkNotUsed() throws IllegalStateException {
645             if ((mBuilderFieldsSet & IS_USED_FIELD_SET) != 0) {
646                 throw new IllegalStateException(
647                         "This Builder should not be reused. Use a new Builder instance instead");
648             }
649         }
650     }
651 }
652