1 /*
2  * Copyright (C) 2024 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 android.annotation.FlaggedApi;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.SystemApi;
23 import android.car.feature.Flags;
24 import android.os.Parcelable;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 
28 import java.lang.annotation.Retention;
29 import java.lang.annotation.RetentionPolicy;
30 import java.util.Objects;
31 
32 /**
33  * Class to encapsulate the features available for the car audio system and should be considered
34  * by the OEM car audio service.
35  *
36  * @hide
37  */
38 @SystemApi
39 @FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES)
40 public final class CarAudioFeaturesInfo implements Parcelable {
41 
42     /**
43      * Used to indicate that no feature is supported by the audio system
44      *
45      * <p><b>Note</b> Only use internally to create a feature object with no features available.
46      *
47      * @hide
48      */
49     public static final int AUDIO_FEATURE_NO_FEATURE = 0x0;
50 
51     /**
52      * Can be used to determined if the audio focus request for playback on dynamic devices should
53      * be treated independently from the rest of the car audio zone. If the feature is enabled,
54      * focus requests should only interact if the focus requests are for the same zone and
55      * targeting playback will be routed to the same device. Otherwise, the focus requests should
56      * interact only if the requests are for the same audio zone.
57      */
58     public static final int AUDIO_FEATURE_ISOLATED_DEVICE_FOCUS = 0x1 << 0;
59 
60     /**
61      * Can be used to determine if fade manager configurations are supported. If the feature is
62      * available, transient fade manager configurations can be provided as part of the focus request
63      * result. These shall be used to fade players whose focus state changed as part of the focus
64      * evaluation. If the feature is not available, any provided fade manager configurations will
65      * be ignored.
66      */
67     public static final int AUDIO_FEATURE_FADE_MANAGER_CONFIGS = 0x1 << 1;
68 
69     /** @hide */
70     @IntDef(flag = true, prefix = "AUDIO_FEATURE", value = {
71             AUDIO_FEATURE_NO_FEATURE,
72             AUDIO_FEATURE_ISOLATED_DEVICE_FOCUS,
73             AUDIO_FEATURE_FADE_MANAGER_CONFIGS,
74     })
75     @Retention(RetentionPolicy.SOURCE)
76     public @interface AudioFeature {}
77 
78     private final int mCarAudioFeatures;
79 
CarAudioFeaturesInfo(int carAudioFeatures)80     CarAudioFeaturesInfo(int carAudioFeatures) {
81         mCarAudioFeatures = checkFeatures(carAudioFeatures);
82     }
83 
84     /**
85      * Determines if a particular audio feature is enabled and should be supported
86      *
87      * @param feature Feature to query, can be {@link #AUDIO_FEATURE_ISOLATED_DEVICE_FOCUS}
88      *                or {@link #AUDIO_FEATURE_FADE_MANAGER_CONFIGS}
89      * @return {@code true} if the feature is enabled, {@code false} otherwise
90      */
isAudioFeatureEnabled(@udioFeature int feature)91     public boolean isAudioFeatureEnabled(@AudioFeature int feature) {
92         return (mCarAudioFeatures & feature) == feature;
93     }
94 
95     @Override
toString()96     public String toString() {
97         return "CarAudioFeaturesInfo { features = " +  featuresToString(mCarAudioFeatures) + " }";
98     }
99 
100     @Override
writeToParcel(@onNull android.os.Parcel dest, int flags)101     public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
102         dest.writeInt(mCarAudioFeatures);
103     }
104 
105     @Override
describeContents()106     public int describeContents() {
107         return 0;
108     }
109 
110     /** @hide */
111     @VisibleForTesting
CarAudioFeaturesInfo(@onNull android.os.Parcel in)112     public CarAudioFeaturesInfo(@NonNull android.os.Parcel in) {
113         mCarAudioFeatures = in.readInt();
114     }
115 
116     @NonNull
117     public static final Creator<CarAudioFeaturesInfo> CREATOR = new Creator<>() {
118         @Override
119         public CarAudioFeaturesInfo[] newArray(int size) {
120             return new CarAudioFeaturesInfo[size];
121         }
122 
123         @Override
124         public CarAudioFeaturesInfo createFromParcel(@NonNull android.os.Parcel in) {
125             return new CarAudioFeaturesInfo(in);
126         }
127     };
128 
129     @Override
equals(Object o)130     public boolean equals(Object o) {
131         if (this == o) {
132             return true;
133         }
134 
135         if (!(o instanceof CarAudioFeaturesInfo)) {
136             return false;
137         }
138 
139         CarAudioFeaturesInfo that = (CarAudioFeaturesInfo) o;
140         return mCarAudioFeatures == that.mCarAudioFeatures;
141     }
142 
143     @Override
hashCode()144     public int hashCode() {
145         return Objects.hash(mCarAudioFeatures);
146     }
147 
featuresToString(int features)148     private static String featuresToString(int features) {
149         if (features == AUDIO_FEATURE_NO_FEATURE) {
150             return "NONE";
151         }
152         StringBuilder builder = new StringBuilder();
153         if (checkFeature(features, AUDIO_FEATURE_ISOLATED_DEVICE_FOCUS)) {
154             builder.append("ISOLATED_DEVICE_FOCUS");
155         }
156         if (checkFeature(features, AUDIO_FEATURE_FADE_MANAGER_CONFIGS)) {
157             builder.append(builder.isEmpty() ? "" : "|");
158             builder.append("FADE_MANAGER_CONFIGS");
159         }
160         return builder.toString();
161     }
162 
checkFeatures(int carAudioFeatures)163     private static int checkFeatures(int carAudioFeatures) {
164         if (carAudioFeatures == AUDIO_FEATURE_NO_FEATURE) {
165             return carAudioFeatures;
166         }
167         int tempFeatures = carAudioFeatures;
168         tempFeatures = tempFeatures & ~AUDIO_FEATURE_ISOLATED_DEVICE_FOCUS;
169         tempFeatures = tempFeatures & ~AUDIO_FEATURE_FADE_MANAGER_CONFIGS;
170         if (tempFeatures != 0) {
171             throw new IllegalArgumentException("Car audio features " + tempFeatures
172                     + " are invalid");
173         }
174         return carAudioFeatures;
175     }
176 
checkFeature(int feature)177     private static int checkFeature(int feature) {
178         if (feature == AUDIO_FEATURE_NO_FEATURE) {
179             return feature;
180         }
181         if (checkFeature(feature, AUDIO_FEATURE_ISOLATED_DEVICE_FOCUS)) {
182             return feature;
183         }
184         if (checkFeature(feature, AUDIO_FEATURE_FADE_MANAGER_CONFIGS)) {
185             return feature;
186         }
187         throw new IllegalArgumentException("Car audio feature " + feature + " is invalid");
188     }
189 
checkFeature(int feature, int checkFeature)190     private static boolean checkFeature(int feature, int checkFeature) {
191         return (feature & checkFeature) == checkFeature;
192     }
193 
194     /**
195      * A builder for {@link CarAudioFeaturesInfo}
196      *
197      * @hide
198      */
199     public static final class Builder {
200 
201         private boolean mBuilderUsed = false;
202         private int mAudioFeatures;
203 
Builder(@onNull CarAudioFeaturesInfo entry)204         public Builder(@NonNull CarAudioFeaturesInfo entry) {
205             this(entry.mCarAudioFeatures);
206         }
207 
Builder(int audioFeatures)208         public Builder(int audioFeatures) {
209             mAudioFeatures = checkFeatures(audioFeatures);
210         }
211 
212         /**
213          * Adds an audio feature to the audio features info
214          *
215          * @param feature Feature to append, can be {@link #AUDIO_FEATURE_ISOLATED_DEVICE_FOCUS}
216          *                or {@link #AUDIO_FEATURE_FADE_MANAGER_CONFIGS}
217          * @throws IllegalArgumentException if the feature parameter is not a supported feature
218          * @throws IllegalStateException if the builder is re-used after calling {@link #build()}
219          */
addAudioFeature(int feature)220         public @NonNull Builder addAudioFeature(int feature) {
221             checkNotUsed();
222             mAudioFeatures |= checkFeature(feature);
223             return this;
224         }
225 
226         /** Builds the instance. This builder should not be touched after calling this! */
build()227         public @NonNull CarAudioFeaturesInfo build() {
228             checkNotUsed();
229             mBuilderUsed = true;
230             return new CarAudioFeaturesInfo(mAudioFeatures);
231         }
232 
checkNotUsed()233         private void checkNotUsed() {
234             if (mBuilderUsed) {
235                 throw new IllegalStateException(
236                         "This Builder should not be reused. Use a new Builder instance instead");
237             }
238         }
239     }
240 }
241