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