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