• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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