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