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 toString()117 public String toString() { 118 return new StringBuilder().append("OemCarAudioFocusResult { audioFocusEntry = ") 119 .append(mAudioFocusEntry) 120 .append(", mNewlyLostAudioFocusEntries = ").append(mNewlyLostAudioFocusEntries) 121 .append(", mNewlyBlockedAudioFocusEntries = ") 122 .append(mNewlyBlockedAudioFocusEntries) 123 .append(", mAudioFocusResult = ").append(mAudioFocusResult) 124 .append(" }").toString(); 125 } 126 127 @Override 128 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_3, 129 minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0) writeToParcel(@onNull Parcel dest, int flags)130 public void writeToParcel(@NonNull Parcel dest, int flags) { 131 byte flg = 0; 132 if (mAudioFocusEntry != null) { 133 flg = (byte) (flg | Builder.FOCUS_ENTRY_FIELDS_SET); 134 } 135 dest.writeByte(flg); 136 if (mAudioFocusEntry != null) { 137 mAudioFocusEntry.writeToParcel(dest, flags); 138 } 139 dest.writeParcelableList(mNewlyLostAudioFocusEntries, flags); 140 dest.writeParcelableList(mNewlyBlockedAudioFocusEntries, flags); 141 dest.writeInt(mAudioFocusResult); 142 } 143 144 // TODO(b/260757994): Remove ApiRequirements for overridden methods 145 @Override 146 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_3, 147 minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0) describeContents()148 public int describeContents() { 149 return 0; 150 } 151 152 /** @hide */ 153 @SuppressWarnings({"unchecked", "RedundantCast"}) 154 @VisibleForTesting OemCarAudioFocusResult(@onNull Parcel in)155 public OemCarAudioFocusResult(@NonNull Parcel in) { 156 byte flg = in.readByte(); 157 AudioFocusEntry audioFocusEntry = (flg & Builder.FOCUS_ENTRY_FIELDS_SET) == 0 158 ? null : AudioFocusEntry.CREATOR.createFromParcel(in); 159 List<AudioFocusEntry> audioFocusLosers = new ArrayList<>(); 160 in.readParcelableList(audioFocusLosers, AudioFocusEntry.class.getClassLoader(), 161 AudioFocusEntry.class); 162 List<AudioFocusEntry> audioFocusBlocked = new ArrayList<>(); 163 in.readParcelableList(audioFocusBlocked, AudioFocusEntry.class.getClassLoader(), 164 AudioFocusEntry.class); 165 int audioFocusResult = in.readInt(); 166 167 this.mAudioFocusEntry = audioFocusEntry; 168 this.mNewlyLostAudioFocusEntries = audioFocusLosers; 169 this.mNewlyBlockedAudioFocusEntries = audioFocusBlocked; 170 this.mAudioFocusResult = audioFocusResult; 171 } 172 173 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_3, 174 minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0) 175 @NonNull 176 public static final OemCarAudioFocusResult EMPTY_OEM_CAR_AUDIO_FOCUS_RESULTS = 177 new OemCarAudioFocusResult(null, 178 /* newlyLostAudioFocusEntries= */ new ArrayList<>(/* initialCapacity= */ 0), 179 /* newlyBlockedAudioFocusEntries= */ new ArrayList<>(/* initialCapacity= */ 0), 180 AUDIOFOCUS_REQUEST_FAILED); 181 182 @Override equals(Object o)183 public boolean equals(Object o) { 184 if (this == o) { 185 return true; 186 } 187 188 if (!(o instanceof OemCarAudioFocusResult)) { 189 return false; 190 } 191 192 OemCarAudioFocusResult that = (OemCarAudioFocusResult) o; 193 194 return Objects.equals(mAudioFocusEntry, that.mAudioFocusEntry) 195 && mAudioFocusResult == that.mAudioFocusResult 196 && mNewlyBlockedAudioFocusEntries.equals( 197 that.mNewlyBlockedAudioFocusEntries) 198 && mNewlyLostAudioFocusEntries.equals(that.mNewlyLostAudioFocusEntries); 199 } 200 201 @Override hashCode()202 public int hashCode() { 203 return Objects.hash(mAudioFocusEntry, mAudioFocusResult, 204 mNewlyBlockedAudioFocusEntries, mNewlyLostAudioFocusEntries); 205 } 206 207 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_3, 208 minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0) 209 @NonNull 210 public static final Parcelable.Creator<OemCarAudioFocusResult> CREATOR = 211 new Parcelable.Creator<OemCarAudioFocusResult>() { 212 @Override 213 public OemCarAudioFocusResult[] newArray(int size) { 214 return new OemCarAudioFocusResult[size]; 215 } 216 217 @Override 218 public OemCarAudioFocusResult createFromParcel(@NonNull Parcel in) { 219 return new OemCarAudioFocusResult(in); 220 } 221 }; 222 223 /** 224 * A builder for {@link OemCarAudioFocusResult} 225 */ 226 @SuppressWarnings("WeakerAccess") 227 public static final class Builder { 228 229 private static final int FOCUS_ENTRY_FIELDS_SET = 0x1; 230 private static final int NEWLY_LOSS_FIELDS_SET = 0x2; 231 private static final int NEWLY_BLOCKED_FIELDS_SET = 0x4; 232 private static final int FOCUS_RESULT_FIELDS_SET = 0x8; 233 private static final int BUILDER_USED_FIELDS_SET = 0x10; 234 private @Nullable AudioFocusEntry mAudioFocusEntry; 235 private @NonNull List<AudioFocusEntry> mNewlyLostAudioFocusEntries; 236 private @NonNull List<AudioFocusEntry> mNewlyBlockedAudioFocusEntries; 237 private int mAudioFocusResult; 238 239 private long mBuilderFieldsSet = 0L; 240 Builder( @onNull List<AudioFocusEntry> newlyLostAudioFocusEntries, @NonNull List<AudioFocusEntry> newlyBlockedAudioFocusEntries, int audioFocusResult)241 public Builder( 242 @NonNull List<AudioFocusEntry> newlyLostAudioFocusEntries, 243 @NonNull List<AudioFocusEntry> newlyBlockedAudioFocusEntries, 244 int audioFocusResult) { 245 Preconditions.checkArgument(newlyLostAudioFocusEntries != null, 246 "Newly lost focus entries can not be null"); 247 Preconditions.checkArgument(newlyBlockedAudioFocusEntries != null, 248 "Newly blocked focus entries can not be null"); 249 mNewlyLostAudioFocusEntries = newlyLostAudioFocusEntries; 250 mNewlyBlockedAudioFocusEntries = newlyBlockedAudioFocusEntries; 251 mAudioFocusResult = audioFocusResult; 252 } 253 254 /** @see OemCarAudioFocusResult#getAudioFocusEntry */ 255 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_3, 256 minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0) 257 @NonNull setAudioFocusEntry(@onNull AudioFocusEntry focusEntry)258 public Builder setAudioFocusEntry(@NonNull AudioFocusEntry focusEntry) { 259 Preconditions.checkArgument(focusEntry != null, 260 "Focus entry can not be null"); 261 checkNotUsed(); 262 mBuilderFieldsSet |= FOCUS_ENTRY_FIELDS_SET; 263 mAudioFocusEntry = focusEntry; 264 return this; 265 } 266 267 /** @see OemCarAudioFocusResult#getNewlyLostAudioFocusEntries */ 268 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_3, 269 minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0) 270 @NonNull setNewlyLostAudioFocusEntries( @onNull List<AudioFocusEntry> newlyLostAudioFocusEntries)271 public Builder setNewlyLostAudioFocusEntries( 272 @NonNull List<AudioFocusEntry> newlyLostAudioFocusEntries) { 273 Preconditions.checkArgument(newlyLostAudioFocusEntries != null, 274 "Newly lost focus entries can not be null"); 275 checkNotUsed(); 276 mBuilderFieldsSet |= NEWLY_LOSS_FIELDS_SET; 277 mNewlyLostAudioFocusEntries = newlyLostAudioFocusEntries; 278 return this; 279 } 280 281 /** @see #setNewlyLostAudioFocusEntries */ 282 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_3, 283 minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0) 284 @NonNull addNewlyLostAudioFocusEntry(@onNull AudioFocusEntry lossEntry)285 public Builder addNewlyLostAudioFocusEntry(@NonNull AudioFocusEntry lossEntry) { 286 Preconditions.checkArgument(lossEntry != null, 287 "Newly lost focus entry can not be null"); 288 if (mNewlyLostAudioFocusEntries == null) { 289 setNewlyLostAudioFocusEntries(new ArrayList<>()); 290 } 291 mNewlyLostAudioFocusEntries.add(lossEntry); 292 return this; 293 } 294 295 /** @see OemCarAudioFocusResult#getNewlyBlockedAudioFocusEntries */ 296 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_3, 297 minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0) 298 @NonNull setNewlyBlockedAudioFocusEntries( @onNull List<AudioFocusEntry> newlyBlockedAudioFocusEntries)299 public Builder setNewlyBlockedAudioFocusEntries( 300 @NonNull List<AudioFocusEntry> newlyBlockedAudioFocusEntries) { 301 Preconditions.checkArgument(newlyBlockedAudioFocusEntries != null, 302 "Newly blocked focus entries can not be null"); 303 checkNotUsed(); 304 mBuilderFieldsSet |= NEWLY_BLOCKED_FIELDS_SET; 305 mNewlyBlockedAudioFocusEntries = newlyBlockedAudioFocusEntries; 306 return this; 307 } 308 309 /** @see #setNewlyBlockedAudioFocusEntries */ 310 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_3, 311 minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0) 312 @NonNull addNewlyBlockedAudioFocusEntry( @onNull AudioFocusEntry blockedEntry)313 public Builder addNewlyBlockedAudioFocusEntry( 314 @NonNull AudioFocusEntry blockedEntry) { 315 Preconditions.checkArgument(blockedEntry != null, 316 "Newly blocked focus entry can not be null"); 317 if (mNewlyBlockedAudioFocusEntries == null) { 318 setNewlyBlockedAudioFocusEntries(new ArrayList<>()); 319 } 320 mNewlyBlockedAudioFocusEntries.add(blockedEntry); 321 return this; 322 } 323 324 /** @see OemCarAudioFocusResult#getAudioFocusResult */ 325 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_3, 326 minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0) 327 @NonNull setAudioFocusResult(int audioFocusResult)328 public Builder setAudioFocusResult(int audioFocusResult) { 329 mBuilderFieldsSet |= FOCUS_RESULT_FIELDS_SET; 330 mAudioFocusResult = audioFocusResult; 331 return this; 332 } 333 334 /** Builds the instance. This builder should not be touched after calling this! */ 335 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_3, 336 minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0) 337 @NonNull build()338 public OemCarAudioFocusResult build() { 339 checkNotUsed(); 340 mBuilderFieldsSet |= BUILDER_USED_FIELDS_SET; // Mark builder used 341 342 OemCarAudioFocusResult o = new OemCarAudioFocusResult( 343 mAudioFocusEntry, 344 mNewlyLostAudioFocusEntries, 345 mNewlyBlockedAudioFocusEntries, 346 mAudioFocusResult); 347 return o; 348 } 349 checkNotUsed()350 private void checkNotUsed() { 351 if ((mBuilderFieldsSet & BUILDER_USED_FIELDS_SET) != 0) { 352 throw new IllegalStateException( 353 "This Builder should not be reused. Use a new Builder instance instead"); 354 } 355 } 356 } 357 } 358