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 com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE; 20 21 import android.annotation.FlaggedApi; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.SystemApi; 25 import android.car.feature.Flags; 26 import android.car.media.CarVolumeGroupInfo; 27 import android.os.Parcel; 28 import android.os.Parcelable; 29 30 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.internal.util.Preconditions; 33 34 import java.util.ArrayList; 35 import java.util.List; 36 import java.util.Objects; 37 38 /** 39 * Class to encapsulate the audio focus evaluation to the OEM audio service 40 * 41 * @hide 42 */ 43 @SystemApi 44 public final class OemCarAudioFocusEvaluationRequest implements Parcelable { 45 46 private @Nullable final AudioFocusEntry mAudioFocusRequest; 47 private @NonNull final List<CarVolumeGroupInfo> mMutedVolumeGroups; 48 private @NonNull final List<AudioFocusEntry> mFocusHolders; 49 private @NonNull final List<AudioFocusEntry> mFocusLosers; 50 private final int mAudioZoneId; 51 private @Nullable final CarAudioFeaturesInfo mCarAudioFeaturesInfo; 52 53 /** 54 * @hide 55 */ 56 @VisibleForTesting OemCarAudioFocusEvaluationRequest(Parcel in)57 public OemCarAudioFocusEvaluationRequest(Parcel in) { 58 byte flg = in.readByte(); 59 mAudioFocusRequest = (flg & Builder.FOCUS_REQUEST_FIELDS_SET) == 0 60 ? null : AudioFocusEntry.CREATOR.createFromParcel(in); 61 mMutedVolumeGroups = new ArrayList<>(); 62 in.readParcelableList(mMutedVolumeGroups, CarVolumeGroupInfo.class.getClassLoader()); 63 mFocusHolders = new ArrayList<>(); 64 in.readParcelableList(mFocusHolders, AudioFocusEntry.class.getClassLoader()); 65 mFocusLosers = new ArrayList<>(); 66 in.readParcelableList(mFocusLosers, AudioFocusEntry.class.getClassLoader()); 67 if (Flags.carAudioDynamicDevices()) { 68 mCarAudioFeaturesInfo = in.readParcelable(CarAudioFeaturesInfo.class.getClassLoader(), 69 CarAudioFeaturesInfo.class); 70 } else { 71 mCarAudioFeaturesInfo = null; 72 } 73 mAudioZoneId = in.readInt(); 74 } 75 76 @NonNull 77 public static final Creator<OemCarAudioFocusEvaluationRequest> CREATOR = 78 new Creator<>() { 79 @Override 80 public OemCarAudioFocusEvaluationRequest createFromParcel(Parcel in) { 81 return new OemCarAudioFocusEvaluationRequest(in); 82 } 83 84 @Override 85 public OemCarAudioFocusEvaluationRequest[] newArray(int size) { 86 return new OemCarAudioFocusEvaluationRequest[size]; 87 } 88 }; 89 90 91 @Override 92 @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) describeContents()93 public int describeContents() { 94 return 0; 95 } 96 97 @Override 98 @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) writeToParcel(@onNull Parcel dest, int flags)99 public void writeToParcel(@NonNull Parcel dest, int flags) { 100 byte flg = 0; 101 if (mAudioFocusRequest != null) { 102 flg = (byte) (flg | Builder.FOCUS_REQUEST_FIELDS_SET); 103 } 104 dest.writeByte(flg); 105 if (mAudioFocusRequest != null) { 106 mAudioFocusRequest.writeToParcel(dest, flags); 107 } 108 dest.writeParcelableList(mMutedVolumeGroups, flags); 109 dest.writeParcelableList(mFocusHolders, flags); 110 dest.writeParcelableList(mFocusLosers, flags); 111 if (Flags.carAudioDynamicDevices()) { 112 dest.writeParcelable(mCarAudioFeaturesInfo, flags); 113 } 114 dest.writeInt(mAudioZoneId); 115 } 116 117 /** 118 * Returns the audio zone id for the request 119 */ getAudioZoneId()120 public int getAudioZoneId() { 121 return mAudioZoneId; 122 } 123 124 /** 125 * Returns the current audio focus info to evaluate, 126 * in cases where the audio focus info is null 127 * the request is to re-evaluate current focus holder and losers. 128 */ getAudioFocusRequest()129 public @Nullable AudioFocusEntry getAudioFocusRequest() { 130 return mAudioFocusRequest; 131 } 132 133 /** 134 * Returns the currently muted volume groups 135 */ getMutedVolumeGroups()136 public @NonNull List<CarVolumeGroupInfo> getMutedVolumeGroups() { 137 return mMutedVolumeGroups; 138 } 139 140 /** 141 * Returns the current focus holder 142 */ getFocusHolders()143 public @NonNull List<AudioFocusEntry> getFocusHolders() { 144 return mFocusHolders; 145 } 146 147 /** 148 * Returns the current focus losers (.i.e focus request that have transiently lost focus) 149 */ getFocusLosers()150 public @NonNull List<AudioFocusEntry> getFocusLosers() { 151 return mFocusLosers; 152 } 153 154 /** 155 * Returns the audio features that could be supported by the request, 156 * see {@link CarAudioFeaturesInfo#isAudioFeatureEnabled(int)} for further info. 157 */ 158 @FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) 159 @Nullable getAudioFeaturesInfo()160 public CarAudioFeaturesInfo getAudioFeaturesInfo() { 161 return mCarAudioFeaturesInfo; 162 } 163 164 @Override equals(Object o)165 public boolean equals(Object o) { 166 if (this == o) { 167 return true; 168 } 169 170 if (!(o instanceof OemCarAudioFocusEvaluationRequest)) { 171 return false; 172 } 173 174 OemCarAudioFocusEvaluationRequest that = (OemCarAudioFocusEvaluationRequest) o; 175 return safeEquals(mAudioFocusRequest, that.mAudioFocusRequest) 176 && mFocusHolders.equals(that.mFocusHolders) 177 && mFocusLosers.equals(that.mFocusLosers) 178 && mMutedVolumeGroups.equals(that.mMutedVolumeGroups) 179 && mAudioZoneId == that.mAudioZoneId 180 && featuresMatches(that); 181 } 182 featuresMatches(OemCarAudioFocusEvaluationRequest that)183 private boolean featuresMatches(OemCarAudioFocusEvaluationRequest that) { 184 return !Flags.carAudioDynamicDevices() 185 || Objects.equals(mCarAudioFeaturesInfo, that.mCarAudioFeaturesInfo); 186 } 187 188 @Override hashCode()189 public int hashCode() { 190 int hash = Objects.hash(mAudioFocusRequest, mFocusHolders, mFocusLosers, mMutedVolumeGroups, 191 mAudioZoneId); 192 if (Flags.carAudioDynamicDevices()) { 193 hash = Objects.hash(hash, mCarAudioFeaturesInfo); 194 } 195 return hash; 196 } 197 198 /** 199 * @hide 200 */ 201 @VisibleForTesting OemCarAudioFocusEvaluationRequest( @ullable AudioFocusEntry audioFocusEntry, @NonNull List<CarVolumeGroupInfo> mutedVolumeGroups, @NonNull List<AudioFocusEntry> focusHolders, @NonNull List<AudioFocusEntry> focusLosers, @Nullable CarAudioFeaturesInfo carAudioFeaturesInfo, int audioZoneId)202 public OemCarAudioFocusEvaluationRequest( 203 @Nullable AudioFocusEntry audioFocusEntry, 204 @NonNull List<CarVolumeGroupInfo> mutedVolumeGroups, 205 @NonNull List<AudioFocusEntry> focusHolders, 206 @NonNull List<AudioFocusEntry> focusLosers, 207 @Nullable CarAudioFeaturesInfo carAudioFeaturesInfo, 208 int audioZoneId) { 209 mAudioFocusRequest = audioFocusEntry; 210 Preconditions.checkArgument(mutedVolumeGroups != null, 211 "Muted volume groups can not be null"); 212 Preconditions.checkArgument(focusHolders != null, 213 "Focus holders can not be null"); 214 Preconditions.checkArgument(focusLosers != null, 215 "Focus losers can not be null"); 216 mMutedVolumeGroups = mutedVolumeGroups; 217 mFocusHolders = focusHolders; 218 mFocusLosers = focusLosers; 219 mAudioZoneId = audioZoneId; 220 mCarAudioFeaturesInfo = carAudioFeaturesInfo; 221 } 222 223 @Override toString()224 public String toString() { 225 String string = "OemCarAudioFocusEvaluationRequest {audioZoneId = " 226 + mAudioZoneId + ", audioFocusInfo = " + mAudioFocusRequest 227 + ", mutedVolumeGroups = " + mMutedVolumeGroups 228 + ", focusHolders = " + mFocusHolders 229 + ", focusLosers = " + mFocusLosers; 230 if (Flags.carAudioDynamicDevices()) { 231 string += " carAudioFeatureInfo " + mCarAudioFeaturesInfo; 232 } 233 return string + " }"; 234 } 235 236 237 /** 238 * A builder for {@link OemCarAudioFocusEvaluationRequest} 239 */ 240 @SuppressWarnings("WeakerAccess") 241 public static final class Builder { 242 243 private static final int FOCUS_REQUEST_FIELDS_SET = 0x1; 244 private static final int BUILDER_USED_FIELDS_SET = 0x20; 245 246 private int mAudioZoneId; 247 private @Nullable CarAudioFeaturesInfo mAudioFeatureInfo; 248 private @Nullable AudioFocusEntry mAudioFocusRequest; 249 private @NonNull List<CarVolumeGroupInfo> mMutedVolumeGroups; 250 private @NonNull List<AudioFocusEntry> mFocusHolders; 251 private @NonNull List<AudioFocusEntry> mFocusLosers; 252 253 private long mBuilderFieldsSet = 0L; 254 Builder( @onNull List<CarVolumeGroupInfo> mutedVolumeGroups, @NonNull List<AudioFocusEntry> focusHolders, @NonNull List<AudioFocusEntry> focusLosers, int audioZoneId)255 public Builder( 256 @NonNull List<CarVolumeGroupInfo> mutedVolumeGroups, 257 @NonNull List<AudioFocusEntry> focusHolders, 258 @NonNull List<AudioFocusEntry> focusLosers, 259 int audioZoneId) { 260 Preconditions.checkArgument(mutedVolumeGroups != null, 261 "Muted volume groups can not be null"); 262 Preconditions.checkArgument(focusHolders != null, 263 " Focus holders can not be null"); 264 Preconditions.checkArgument(focusLosers != null, 265 "Focus losers can not be null"); 266 mMutedVolumeGroups = mutedVolumeGroups; 267 mFocusHolders = focusHolders; 268 mFocusLosers = focusLosers; 269 mAudioZoneId = audioZoneId; 270 } 271 272 /** 273 * set the audio zone id 274 */ setAudioZoneId(int value)275 public @NonNull Builder setAudioZoneId(int value) { 276 checkNotUsed(); 277 mAudioZoneId = value; 278 return this; 279 } 280 281 /** 282 * Sets the audio feature which should be supported for the focus request 283 * 284 * @param featuresInfo Feature that should be supported 285 */ 286 @FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) setAudioFeaturesInfo(@onNull CarAudioFeaturesInfo featuresInfo)287 public @NonNull Builder setAudioFeaturesInfo(@NonNull CarAudioFeaturesInfo featuresInfo) { 288 checkNotUsed(); 289 mAudioFeatureInfo = Objects.requireNonNull(featuresInfo, 290 "Car audio features can not be null"); 291 return this; 292 } 293 294 /** 295 * Sets the current focus info to evaluate 296 */ 297 @NonNull setAudioFocusRequest(@onNull AudioFocusEntry audioFocusRequest)298 public Builder setAudioFocusRequest(@NonNull AudioFocusEntry audioFocusRequest) { 299 Preconditions.checkArgument(audioFocusRequest != null, 300 "Audio focus request can not be null"); 301 checkNotUsed(); 302 mAudioFocusRequest = audioFocusRequest; 303 return this; 304 } 305 306 /** 307 * Sets the currently muted group volumes 308 */ 309 @NonNull setMutedVolumeGroups( @onNull List<CarVolumeGroupInfo> mutedVolumeGroups)310 public Builder setMutedVolumeGroups( 311 @NonNull List<CarVolumeGroupInfo> mutedVolumeGroups) { 312 Preconditions.checkArgument(mutedVolumeGroups != null, 313 "Muted volume groups can not be null"); 314 checkNotUsed(); 315 mMutedVolumeGroups = mutedVolumeGroups; 316 return this; 317 } 318 319 /** @see #setMutedVolumeGroups */ addMutedVolumeGroups(@onNull CarVolumeGroupInfo mutedVolumeGroup)320 public @NonNull Builder addMutedVolumeGroups(@NonNull CarVolumeGroupInfo mutedVolumeGroup) { 321 Preconditions.checkArgument(mutedVolumeGroup != null, 322 "Muted volume group can not be null"); 323 if (mMutedVolumeGroups == null) setMutedVolumeGroups(new ArrayList<>()); 324 mMutedVolumeGroups.add(mutedVolumeGroup); 325 return this; 326 } 327 328 /** 329 * Sets the focus holders 330 */ setFocusHolders(@onNull List<AudioFocusEntry> focusHolders)331 public @NonNull Builder setFocusHolders(@NonNull List<AudioFocusEntry> focusHolders) { 332 Preconditions.checkArgument(focusHolders != null, 333 "Focus holders can not be null"); 334 checkNotUsed(); 335 mFocusHolders = focusHolders; 336 return this; 337 } 338 339 /** @see #setFocusHolders */ addFocusHolders(@onNull AudioFocusEntry focusHolder)340 public @NonNull Builder addFocusHolders(@NonNull AudioFocusEntry focusHolder) { 341 Preconditions.checkArgument(focusHolder != null, 342 "Focus holder can not be null"); 343 if (mFocusHolders == null) setFocusHolders(new ArrayList<>()); 344 mFocusHolders.add(focusHolder); 345 return this; 346 } 347 348 /** 349 * Sets the focus losers 350 */ setFocusLosers(@onNull List<AudioFocusEntry> focusLosers)351 public @NonNull Builder setFocusLosers(@NonNull List<AudioFocusEntry> focusLosers) { 352 Preconditions.checkArgument(focusLosers != null, 353 "Focus losers can not be null"); 354 checkNotUsed(); 355 mFocusLosers = focusLosers; 356 return this; 357 } 358 359 /** @see #setFocusLosers */ addFocusLosers(@onNull AudioFocusEntry focusLoser)360 public @NonNull Builder addFocusLosers(@NonNull AudioFocusEntry focusLoser) { 361 Preconditions.checkArgument(focusLoser != null, 362 "Focus loser can not be null"); 363 if (mFocusLosers == null) setFocusLosers(new ArrayList<>()); 364 mFocusLosers.add(focusLoser); 365 return this; 366 } 367 368 /** Builds the instance. This builder should not be touched after calling this! */ 369 @NonNull build()370 public OemCarAudioFocusEvaluationRequest build() { 371 checkNotUsed(); 372 mBuilderFieldsSet |= BUILDER_USED_FIELDS_SET; // Mark builder used 373 374 return new OemCarAudioFocusEvaluationRequest(mAudioFocusRequest, mMutedVolumeGroups, 375 mFocusHolders, mFocusLosers, mAudioFeatureInfo, mAudioZoneId); 376 } 377 checkNotUsed()378 private void checkNotUsed() { 379 if ((mBuilderFieldsSet & BUILDER_USED_FIELDS_SET) != 0) { 380 throw new IllegalStateException( 381 "This Builder should not be reused. Use a new Builder instance instead"); 382 } 383 } 384 } 385 safeEquals(Object a, Object b)386 private static boolean safeEquals(Object a, Object b) { 387 return a == b || (a != null && a.equals(b)); 388 } 389 } 390