1 /* 2 * Copyright (C) 2022 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.oem; 18 19 import static android.car.feature.Flags.FLAG_CAR_AUDIO_FADE_MANAGER_CONFIGURATION; 20 import static android.car.feature.Flags.carAudioFadeManagerConfiguration; 21 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_FAILED; 22 23 import android.annotation.FlaggedApi; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.SystemApi; 27 import android.media.AudioAttributes; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 import android.util.ArrayMap; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.internal.util.Preconditions; 34 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Objects; 39 40 /** 41 * Class to encapsulate the audio focus result from the OEM audio service 42 * 43 * @hide 44 */ 45 @SystemApi 46 public final class OemCarAudioFocusResult implements Parcelable { 47 private final @Nullable AudioFocusEntry mAudioFocusEntry; 48 private final @NonNull List<AudioFocusEntry> mNewlyLostAudioFocusEntries; 49 private final @NonNull List<AudioFocusEntry> mNewlyBlockedAudioFocusEntries; 50 private final @NonNull Map<AudioAttributes, 51 CarAudioFadeConfiguration> mAttrsToCarAudioFadeConfig; 52 private final int mAudioFocusResult; 53 OemCarAudioFocusResult( @ullable AudioFocusEntry audioFocusEntry, @NonNull List<AudioFocusEntry> newlyLostAudioFocusEntries, @NonNull List<AudioFocusEntry> newlyBlockedAudioFocusEntries, int audioFocusResult, @NonNull Map<AudioAttributes, CarAudioFadeConfiguration> attrsToCarAudioFadeConfig)54 OemCarAudioFocusResult( 55 @Nullable AudioFocusEntry audioFocusEntry, 56 @NonNull List<AudioFocusEntry> newlyLostAudioFocusEntries, 57 @NonNull List<AudioFocusEntry> newlyBlockedAudioFocusEntries, int audioFocusResult, 58 @NonNull Map<AudioAttributes, CarAudioFadeConfiguration> attrsToCarAudioFadeConfig) { 59 Preconditions.checkArgument(newlyLostAudioFocusEntries != null, 60 "Newly lost focus entries can not be null"); 61 Preconditions.checkArgument(newlyBlockedAudioFocusEntries != null, 62 "Newly blocked focus entries can not be null"); 63 mAudioFocusEntry = audioFocusEntry; 64 mNewlyLostAudioFocusEntries = newlyLostAudioFocusEntries; 65 mNewlyBlockedAudioFocusEntries = newlyBlockedAudioFocusEntries; 66 mAudioFocusResult = audioFocusResult; 67 mAttrsToCarAudioFadeConfig = Objects.requireNonNull(attrsToCarAudioFadeConfig, 68 "Audio attributes to car audio fade configuration can not be null"); 69 } 70 71 /** 72 * Returns the result of the focus request 73 * The result can be granted, delayed, or failed. In the case of granted the car audio stack 74 * will be changed according to the entries returned in newly lost and newly blocked. 75 * For delayed results the entry will be added as the current delayed request and it will be 76 * re-evaluated once any of the current focus holders abandons focus. For failed request, 77 * the car audio focus stack will not change and the current request will not gain focus. 78 */ getAudioFocusEntry()79 public @Nullable AudioFocusEntry getAudioFocusEntry() { 80 return new AudioFocusEntry.Builder(mAudioFocusEntry).build(); 81 } 82 83 /** 84 * Returns the entries that were previously holding focus but now have lost focus. 85 * 86 * <p>Note: the lost can be permanent or transient, in the case of permanent loss the entry 87 * will receive permanent focus loss and it will be removed from the car audio focus stack. 88 * For transient losses, the new entry will be added as a blocker but will only receive 89 * transient focus loss. 90 */ getNewlyLostAudioFocusEntries()91 public @NonNull List<AudioFocusEntry> getNewlyLostAudioFocusEntries() { 92 return new ArrayList<>(mNewlyLostAudioFocusEntries); 93 } 94 95 /** 96 * Returns the entries that had previously lost focus and continue to be blocked by new entry 97 * 98 * <p>Note: the block can be permanent or transient, in the case of permanent block the entry 99 * will receive permanent focus loss and it will be removed from the car audio focus stack. 100 * For transient losses, the new entry will be added as a blocker but will only receive 101 * transient focus loss. 102 */ getNewlyBlockedAudioFocusEntries()103 public @NonNull List<AudioFocusEntry> getNewlyBlockedAudioFocusEntries() { 104 return new ArrayList<>(mNewlyBlockedAudioFocusEntries); 105 } 106 107 /** 108 * Returns the focus results, must be on of {@link AudioManager.AUDIOFOCUS_GAIN}, 109 * {@link AudioManager.AUDIOFOCUS_LOSS}, {@link AudioManager.AUDIOFOCUS_LOSS_TRANSIENT}, 110 * {@link AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} 111 */ getAudioFocusResult()112 public int getAudioFocusResult() { 113 return mAudioFocusResult; 114 } 115 116 /** 117 * Returns the map of transient {@link CarAudioFadeConfiguration} 118 * 119 * @return Map of {@link android.media.AudioAttributes} to 120 * {@link CarAudioFadeConfiguration} when set through {@link Builder} or an empty array if 121 * not set. 122 */ 123 @FlaggedApi(FLAG_CAR_AUDIO_FADE_MANAGER_CONFIGURATION) 124 @NonNull 125 public Map<AudioAttributes, getAudioAttributesToCarAudioFadeConfigurationMap()126 CarAudioFadeConfiguration> getAudioAttributesToCarAudioFadeConfigurationMap() { 127 ensureCarAudioFadeManagerConfigIsEnabled(); 128 return mAttrsToCarAudioFadeConfig; 129 } 130 131 /** 132 * Returns the {@link CarAudioFadeConfiguration} corresponding to the 133 * {@link android.media.AudioAttributes} 134 * 135 * @param audioAttributes The {@link android.media.AudioAttributes} to get the car audio 136 * fade configuration 137 * @return {@link CarAudioFadeConfiguration} if one is available for the 138 * {@link android.media.AudioAttributes} or {@code null} if none is assigned 139 */ 140 @FlaggedApi(FLAG_CAR_AUDIO_FADE_MANAGER_CONFIGURATION) 141 @Nullable getCarAudioFadeConfigurationForAudioAttributes( @onNull AudioAttributes audioAttributes)142 public CarAudioFadeConfiguration getCarAudioFadeConfigurationForAudioAttributes( 143 @NonNull AudioAttributes audioAttributes) { 144 ensureCarAudioFadeManagerConfigIsEnabled(); 145 Objects.requireNonNull(audioAttributes, "Audio attributes can not be null"); 146 return mAttrsToCarAudioFadeConfig.get(audioAttributes); 147 } 148 149 @Override toString()150 public String toString() { 151 return new StringBuilder() 152 .append("OemCarAudioFocusResult { audioFocusEntry = ").append(mAudioFocusEntry) 153 .append(", mNewlyLostAudioFocusEntries = ").append(mNewlyLostAudioFocusEntries) 154 .append(", mNewlyBlockedAudioFocusEntries = ") 155 .append(mNewlyBlockedAudioFocusEntries) 156 .append(", mAudioFocusResult = ").append(mAudioFocusResult) 157 .append(convertAttrsToCarAudioFadeConfigurationMap()) 158 .append(" }").toString(); 159 } 160 161 @Override writeToParcel(@onNull Parcel dest, int flags)162 public void writeToParcel(@NonNull Parcel dest, int flags) { 163 byte flg = 0; 164 if (mAudioFocusEntry != null) { 165 flg = (byte) (flg | Builder.FOCUS_ENTRY_FIELDS_SET); 166 } 167 dest.writeByte(flg); 168 if (mAudioFocusEntry != null) { 169 mAudioFocusEntry.writeToParcel(dest, flags); 170 } 171 dest.writeParcelableList(mNewlyLostAudioFocusEntries, flags); 172 dest.writeParcelableList(mNewlyBlockedAudioFocusEntries, flags); 173 dest.writeInt(mAudioFocusResult); 174 if (carAudioFadeManagerConfiguration()) { 175 dest.writeMap(mAttrsToCarAudioFadeConfig); 176 } 177 } 178 179 // TODO(b/260757994): Remove ApiRequirements for overridden methods 180 @Override describeContents()181 public int describeContents() { 182 return 0; 183 } 184 185 /** @hide */ 186 @SuppressWarnings({"unchecked", "RedundantCast"}) 187 @VisibleForTesting OemCarAudioFocusResult(@onNull Parcel in)188 public OemCarAudioFocusResult(@NonNull Parcel in) { 189 byte flg = in.readByte(); 190 AudioFocusEntry audioFocusEntry = (flg & Builder.FOCUS_ENTRY_FIELDS_SET) == 0 191 ? null : AudioFocusEntry.CREATOR.createFromParcel(in); 192 List<AudioFocusEntry> audioFocusLosers = new ArrayList<>(); 193 in.readParcelableList(audioFocusLosers, AudioFocusEntry.class.getClassLoader(), 194 AudioFocusEntry.class); 195 List<AudioFocusEntry> audioFocusBlocked = new ArrayList<>(); 196 in.readParcelableList(audioFocusBlocked, AudioFocusEntry.class.getClassLoader(), 197 AudioFocusEntry.class); 198 int audioFocusResult = in.readInt(); 199 ArrayMap<AudioAttributes, 200 CarAudioFadeConfiguration> audioAttributesCarAudioFadeConfig = new ArrayMap<>(); 201 if (carAudioFadeManagerConfiguration()) { 202 in.readMap(audioAttributesCarAudioFadeConfig, getClass().getClassLoader(), 203 AudioAttributes.class, CarAudioFadeConfiguration.class); 204 } 205 206 mAudioFocusEntry = audioFocusEntry; 207 mNewlyLostAudioFocusEntries = audioFocusLosers; 208 mNewlyBlockedAudioFocusEntries = audioFocusBlocked; 209 mAudioFocusResult = audioFocusResult; 210 mAttrsToCarAudioFadeConfig = audioAttributesCarAudioFadeConfig; 211 } 212 213 @NonNull 214 public static final OemCarAudioFocusResult EMPTY_OEM_CAR_AUDIO_FOCUS_RESULTS = 215 new OemCarAudioFocusResult(null, 216 /* newlyLostAudioFocusEntries= */ new ArrayList<>(/* initialCapacity= */ 0), 217 /* newlyBlockedAudioFocusEntries= */ new ArrayList<>(/* initialCapacity= */ 0), 218 AUDIOFOCUS_REQUEST_FAILED, 219 /* attrsToCarAudioFadeConfig= */ new ArrayMap<>(/* initialCapacity= */ 0)); 220 221 @Override equals(Object o)222 public boolean equals(Object o) { 223 if (this == o) { 224 return true; 225 } 226 227 if (!(o instanceof OemCarAudioFocusResult)) { 228 return false; 229 } 230 231 OemCarAudioFocusResult that = (OemCarAudioFocusResult) o; 232 233 return Objects.equals(mAudioFocusEntry, that.mAudioFocusEntry) 234 && mAudioFocusResult == that.mAudioFocusResult 235 && mNewlyBlockedAudioFocusEntries.equals( 236 that.mNewlyBlockedAudioFocusEntries) 237 && mNewlyLostAudioFocusEntries.equals(that.mNewlyLostAudioFocusEntries) 238 && Objects.equals(getAttrsToCarAudioFadeConfigMap(), 239 that.getAttrsToCarAudioFadeConfigMap()); 240 } 241 242 @Override hashCode()243 public int hashCode() { 244 return Objects.hash(mAudioFocusEntry, mAudioFocusResult, 245 mNewlyBlockedAudioFocusEntries, mNewlyLostAudioFocusEntries, 246 getAttrsToCarAudioFadeConfigMap()); 247 } 248 249 @NonNull 250 public static final Parcelable.Creator<OemCarAudioFocusResult> CREATOR = 251 new Parcelable.Creator<OemCarAudioFocusResult>() { 252 @Override 253 public OemCarAudioFocusResult[] newArray(int size) { 254 return new OemCarAudioFocusResult[size]; 255 } 256 257 @Override 258 public OemCarAudioFocusResult createFromParcel(@NonNull Parcel in) { 259 return new OemCarAudioFocusResult(in); 260 } 261 }; 262 getAttrsToCarAudioFadeConfigMap()263 private Map<AudioAttributes, CarAudioFadeConfiguration> getAttrsToCarAudioFadeConfigMap() { 264 return carAudioFadeManagerConfiguration() ? mAttrsToCarAudioFadeConfig : null; 265 } 266 convertAttrsToCarAudioFadeConfigurationMap()267 private String convertAttrsToCarAudioFadeConfigurationMap() { 268 return carAudioFadeManagerConfiguration() 269 ? ", mAttrsToCarAudioFadeConfig = " + mAttrsToCarAudioFadeConfig : ""; 270 } 271 ensureCarAudioFadeManagerConfigIsEnabled()272 private static void ensureCarAudioFadeManagerConfigIsEnabled() { 273 Preconditions.checkState(carAudioFadeManagerConfiguration(), 274 "Car audio fade manager configuration not supported"); 275 } 276 277 /** 278 * A builder for {@link OemCarAudioFocusResult} 279 */ 280 @SuppressWarnings("WeakerAccess") 281 public static final class Builder { 282 283 private static final int FOCUS_ENTRY_FIELDS_SET = 0x1; 284 private static final int NEWLY_LOSS_FIELDS_SET = 0x2; 285 private static final int NEWLY_BLOCKED_FIELDS_SET = 0x4; 286 private static final int FOCUS_RESULT_FIELDS_SET = 0x8; 287 private static final int BUILDER_USED_FIELDS_SET = 0x10; 288 private @Nullable AudioFocusEntry mAudioFocusEntry; 289 private @NonNull List<AudioFocusEntry> mNewlyLostAudioFocusEntries; 290 private @NonNull List<AudioFocusEntry> mNewlyBlockedAudioFocusEntries; 291 private int mAudioFocusResult; 292 private final @NonNull Map<AudioAttributes, 293 CarAudioFadeConfiguration> mAttrsToCarAudioFadeConfig = new ArrayMap<>(); 294 295 private long mBuilderFieldsSet = 0L; 296 Builder( @onNull List<AudioFocusEntry> newlyLostAudioFocusEntries, @NonNull List<AudioFocusEntry> newlyBlockedAudioFocusEntries, int audioFocusResult)297 public Builder( 298 @NonNull List<AudioFocusEntry> newlyLostAudioFocusEntries, 299 @NonNull List<AudioFocusEntry> newlyBlockedAudioFocusEntries, 300 int audioFocusResult) { 301 Preconditions.checkArgument(newlyLostAudioFocusEntries != null, 302 "Newly lost focus entries can not be null"); 303 Preconditions.checkArgument(newlyBlockedAudioFocusEntries != null, 304 "Newly blocked focus entries can not be null"); 305 mNewlyLostAudioFocusEntries = newlyLostAudioFocusEntries; 306 mNewlyBlockedAudioFocusEntries = newlyBlockedAudioFocusEntries; 307 mAudioFocusResult = audioFocusResult; 308 } 309 310 /** @see OemCarAudioFocusResult#getAudioFocusEntry */ 311 @NonNull setAudioFocusEntry(@onNull AudioFocusEntry focusEntry)312 public Builder setAudioFocusEntry(@NonNull AudioFocusEntry focusEntry) { 313 Preconditions.checkArgument(focusEntry != null, 314 "Focus entry can not be null"); 315 checkNotUsed(); 316 mBuilderFieldsSet |= FOCUS_ENTRY_FIELDS_SET; 317 mAudioFocusEntry = focusEntry; 318 return this; 319 } 320 321 /** @see OemCarAudioFocusResult#getNewlyLostAudioFocusEntries */ 322 @NonNull setNewlyLostAudioFocusEntries( @onNull List<AudioFocusEntry> newlyLostAudioFocusEntries)323 public Builder setNewlyLostAudioFocusEntries( 324 @NonNull List<AudioFocusEntry> newlyLostAudioFocusEntries) { 325 Preconditions.checkArgument(newlyLostAudioFocusEntries != null, 326 "Newly lost focus entries can not be null"); 327 checkNotUsed(); 328 mBuilderFieldsSet |= NEWLY_LOSS_FIELDS_SET; 329 mNewlyLostAudioFocusEntries = newlyLostAudioFocusEntries; 330 return this; 331 } 332 333 /** @see #setNewlyLostAudioFocusEntries */ 334 @NonNull addNewlyLostAudioFocusEntry(@onNull AudioFocusEntry lossEntry)335 public Builder addNewlyLostAudioFocusEntry(@NonNull AudioFocusEntry lossEntry) { 336 Preconditions.checkArgument(lossEntry != null, 337 "Newly lost focus entry can not be null"); 338 if (mNewlyLostAudioFocusEntries == null) { 339 setNewlyLostAudioFocusEntries(new ArrayList<>()); 340 } 341 mNewlyLostAudioFocusEntries.add(lossEntry); 342 return this; 343 } 344 345 /** @see OemCarAudioFocusResult#getNewlyBlockedAudioFocusEntries */ 346 @NonNull setNewlyBlockedAudioFocusEntries( @onNull List<AudioFocusEntry> newlyBlockedAudioFocusEntries)347 public Builder setNewlyBlockedAudioFocusEntries( 348 @NonNull List<AudioFocusEntry> newlyBlockedAudioFocusEntries) { 349 Preconditions.checkArgument(newlyBlockedAudioFocusEntries != null, 350 "Newly blocked focus entries can not be null"); 351 checkNotUsed(); 352 mBuilderFieldsSet |= NEWLY_BLOCKED_FIELDS_SET; 353 mNewlyBlockedAudioFocusEntries = newlyBlockedAudioFocusEntries; 354 return this; 355 } 356 357 /** @see #setNewlyBlockedAudioFocusEntries */ 358 @NonNull addNewlyBlockedAudioFocusEntry( @onNull AudioFocusEntry blockedEntry)359 public Builder addNewlyBlockedAudioFocusEntry( 360 @NonNull AudioFocusEntry blockedEntry) { 361 Preconditions.checkArgument(blockedEntry != null, 362 "Newly blocked focus entry can not be null"); 363 if (mNewlyBlockedAudioFocusEntries == null) { 364 setNewlyBlockedAudioFocusEntries(new ArrayList<>()); 365 } 366 mNewlyBlockedAudioFocusEntries.add(blockedEntry); 367 return this; 368 } 369 370 /** @see OemCarAudioFocusResult#getAudioFocusResult */ 371 @NonNull setAudioFocusResult(int audioFocusResult)372 public Builder setAudioFocusResult(int audioFocusResult) { 373 mBuilderFieldsSet |= FOCUS_RESULT_FIELDS_SET; 374 mAudioFocusResult = audioFocusResult; 375 return this; 376 } 377 378 /** @see OemCarAudioFocusResult#getAudioAttributesToCarAudioFadeConfigurationMap() **/ 379 @FlaggedApi(FLAG_CAR_AUDIO_FADE_MANAGER_CONFIGURATION) 380 @NonNull setAudioAttributesToCarAudioFadeConfigurationMap(@onNull Map<AudioAttributes, CarAudioFadeConfiguration> attrsToCarAudioFadeConfig)381 public Builder setAudioAttributesToCarAudioFadeConfigurationMap(@NonNull 382 Map<AudioAttributes, CarAudioFadeConfiguration> attrsToCarAudioFadeConfig) { 383 ensureCarAudioFadeManagerConfigIsEnabled(); 384 Objects.requireNonNull(attrsToCarAudioFadeConfig, 385 "Audio attributes to car audio fade configuration map can not be null"); 386 mAttrsToCarAudioFadeConfig.clear(); 387 mAttrsToCarAudioFadeConfig.putAll(attrsToCarAudioFadeConfig); 388 return this; 389 } 390 391 /** Builds the instance. This builder should not be touched after calling this! */ 392 @NonNull build()393 public OemCarAudioFocusResult build() { 394 checkNotUsed(); 395 mBuilderFieldsSet |= BUILDER_USED_FIELDS_SET; // Mark builder used 396 397 OemCarAudioFocusResult o = new OemCarAudioFocusResult( 398 mAudioFocusEntry, 399 mNewlyLostAudioFocusEntries, 400 mNewlyBlockedAudioFocusEntries, 401 mAudioFocusResult, 402 mAttrsToCarAudioFadeConfig); 403 return o; 404 } 405 checkNotUsed()406 private void checkNotUsed() { 407 if ((mBuilderFieldsSet & BUILDER_USED_FIELDS_SET) != 0) { 408 throw new IllegalStateException( 409 "This Builder should not be reused. Use a new Builder instance instead"); 410 } 411 } 412 } 413 } 414