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