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