1 /*
2  * Copyright (C) 2014 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.media.audiopolicy;
18 
19 import android.annotation.NonNull;
20 import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
21 import android.os.Parcel;
22 import android.os.Parcelable;
23 import android.util.Log;
24 import android.util.Pair;
25 
26 import com.android.internal.annotations.GuardedBy;
27 import com.android.internal.annotations.VisibleForTesting;
28 
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Objects;
32 
33 /**
34  * @hide
35  * Internal storage class for AudioPolicy configuration.
36  */
37 public class AudioPolicyConfig implements Parcelable {
38 
39     private static final String TAG = "AudioPolicyConfig";
40 
41     protected final ArrayList<AudioMix> mMixes;
42     protected int mDuckingPolicy = AudioPolicy.FOCUS_POLICY_DUCKING_IN_APP;
43 
44     private String mRegistrationId = null;
45 
46     // Corresponds to id of next mix to be registered.
47     private int mMixCounter = 0;
48 
AudioPolicyConfig(AudioPolicyConfig conf)49     protected AudioPolicyConfig(AudioPolicyConfig conf) {
50         mMixes = conf.mMixes;
51     }
52 
53     @VisibleForTesting
AudioPolicyConfig(ArrayList<AudioMix> mixes)54     public AudioPolicyConfig(ArrayList<AudioMix> mixes) {
55         mMixes = mixes;
56     }
57 
58     /**
59      * Add an {@link AudioMix} to be part of the audio policy being built.
60      * @param mix a non-null {@link AudioMix} to be part of the audio policy.
61      * @return the same Builder instance.
62      * @throws IllegalArgumentException
63      */
addMix(AudioMix mix)64     public void addMix(AudioMix mix) throws IllegalArgumentException {
65         if (mix == null) {
66             throw new IllegalArgumentException("Illegal null AudioMix argument");
67         }
68         mMixes.add(mix);
69     }
70 
getMixes()71     public ArrayList<AudioMix> getMixes() {
72         return mMixes;
73     }
74 
75     @Override
hashCode()76     public int hashCode() {
77         return Objects.hash(mMixes);
78     }
79 
80     @Override
describeContents()81     public int describeContents() {
82         return 0;
83     }
84 
85     @Override
writeToParcel(Parcel dest, int flags)86     public void writeToParcel(Parcel dest, int flags) {
87         dest.writeInt(mMixes.size());
88         for (AudioMix mix : mMixes) {
89             mix.writeToParcel(dest, flags);
90         }
91     }
92 
AudioPolicyConfig(Parcel in)93     private AudioPolicyConfig(Parcel in) {
94         int nbMixes = in.readInt();
95         mMixes = new ArrayList<>(nbMixes);
96         for (int i = 0 ; i < nbMixes ; i++) {
97             mMixes.add(AudioMix.CREATOR.createFromParcel(in));
98         }
99     }
100 
101     public static final @android.annotation.NonNull Parcelable.Creator<AudioPolicyConfig> CREATOR =
102             new Parcelable.Creator<>() {
103         /**
104          * Rebuilds an AudioPolicyConfig previously stored with writeToParcel().
105          * @param p Parcel object to read the AudioPolicyConfig from
106          * @return a new AudioPolicyConfig created from the data in the parcel
107          */
108         public AudioPolicyConfig createFromParcel(Parcel p) {
109             return new AudioPolicyConfig(p);
110         }
111         public AudioPolicyConfig[] newArray(int size) {
112             return new AudioPolicyConfig[size];
113         }
114     };
115 
toLogFriendlyString()116     public String toLogFriendlyString () {
117         String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n");
118         textDump += mMixes.size() + " AudioMix, reg:" + mRegistrationId + "\n";
119         for(AudioMix mix : mMixes) {
120             // write mix route flags
121             textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n";
122             // write mix format
123             textDump += "  rate=" + mix.getFormat().getSampleRate() + "Hz\n";
124             textDump += "  encoding=" + mix.getFormat().getEncoding() + "\n";
125             textDump += "  channels=0x";
126             textDump += Integer.toHexString(mix.getFormat().getChannelMask()).toUpperCase() + "\n";
127             textDump += "  ignore playback capture opt out="
128                     + mix.getRule().allowPrivilegedMediaPlaybackCapture() + "\n";
129             textDump += "  allow voice communication capture="
130                     + mix.getRule().voiceCommunicationCaptureAllowed() + "\n";
131             // write mix rules
132             textDump += "  specified mix type="
133                     + mix.getRule().getTargetMixRole() + "\n";
134             final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
135             for (AudioMixMatchCriterion criterion : criteria) {
136                 switch(criterion.mRule) {
137                     case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE:
138                         textDump += "  exclude usage ";
139                         textDump += criterion.mAttr.usageToString();
140                         break;
141                     case AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE:
142                         textDump += "  match usage ";
143                         textDump += criterion.mAttr.usageToString();
144                         break;
145                     case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
146                         textDump += "  exclude capture preset ";
147                         textDump += criterion.mAttr.getCapturePreset();
148                         break;
149                     case AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
150                         textDump += "  match capture preset ";
151                         textDump += criterion.mAttr.getCapturePreset();
152                         break;
153                     case AudioMixingRule.RULE_MATCH_UID:
154                         textDump += "  match UID ";
155                         textDump += criterion.mIntProp;
156                         break;
157                     case AudioMixingRule.RULE_EXCLUDE_UID:
158                         textDump += "  exclude UID ";
159                         textDump += criterion.mIntProp;
160                         break;
161                     case AudioMixingRule.RULE_MATCH_USERID:
162                         textDump += "  match userId ";
163                         textDump += criterion.mIntProp;
164                         break;
165                     case AudioMixingRule.RULE_EXCLUDE_USERID:
166                         textDump += "  exclude userId ";
167                         textDump += criterion.mIntProp;
168                         break;
169                     case AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID:
170                         textDump += " match audio session id";
171                         textDump += criterion.mIntProp;
172                         break;
173                     case AudioMixingRule.RULE_EXCLUDE_AUDIO_SESSION_ID:
174                         textDump += " exclude audio session id ";
175                         textDump += criterion.mIntProp;
176                         break;
177                     default:
178                         textDump += "invalid rule!";
179                 }
180                 textDump += "\n";
181             }
182         }
183         return textDump;
184     }
185 
186     /**
187      * Very short dump of configuration
188      * @return a condensed dump of configuration, uniquely identifies a policy in a log
189      */
toCompactLogString()190     public String toCompactLogString() {
191         String compactDump = "reg:" + mRegistrationId;
192         int mixNum = 0;
193         for (AudioMix mix : mMixes) {
194             compactDump += " Mix:" + mixNum + "-Typ:" + mixTypePrefix(mix.getMixType())
195                     + "-Rul:" + mix.getRule().getCriteria().size();
196             mixNum++;
197         }
198         return compactDump;
199     }
200 
mixTypePrefix(int mixType)201     private static String mixTypePrefix(int mixType) {
202         switch (mixType) {
203             case AudioMix.MIX_TYPE_PLAYERS:
204                 return "p";
205             case AudioMix.MIX_TYPE_RECORDERS:
206                 return "r";
207             case AudioMix.MIX_TYPE_INVALID:
208             default:
209                 return "#";
210 
211         }
212     }
213 
reset()214     protected void reset() {
215         mMixCounter = 0;
216     }
217 
setRegistration(String regId)218     protected void setRegistration(String regId) {
219         final boolean currentRegNull = (mRegistrationId == null) || mRegistrationId.isEmpty();
220         final boolean newRegNull = (regId == null) || regId.isEmpty();
221         if (!currentRegNull && !newRegNull && !mRegistrationId.equals(regId)) {
222             Log.e(TAG, "Invalid registration transition from " + mRegistrationId + " to " + regId);
223             return;
224         }
225         mRegistrationId = regId == null ? "" : regId;
226         for (AudioMix mix : mMixes) {
227             setMixRegistration(mix);
228         }
229     }
230 
setMixRegistration(@onNull final AudioMix mix)231     protected void setMixRegistration(@NonNull final AudioMix mix) {
232         if (!mRegistrationId.isEmpty()) {
233             if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) ==
234                     AudioMix.ROUTE_FLAG_LOOP_BACK) {
235                 mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
236                         + mMixCounter++);
237             } else if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_RENDER) ==
238                     AudioMix.ROUTE_FLAG_RENDER) {
239                 mix.setRegistration(mix.mDeviceAddress);
240             }
241         } else {
242             mix.setRegistration("");
243         }
244     }
245 
246     @GuardedBy("mMixes")
add(@onNull ArrayList<AudioMix> mixes)247     protected void add(@NonNull ArrayList<AudioMix> mixes) {
248         for (AudioMix mix : mixes) {
249             if (mix.getRegistration() == null || mix.getRegistration().isEmpty()) {
250                 setMixRegistration(mix);
251             }
252             mMixes.add(mix);
253         }
254     }
255 
256     @GuardedBy("mMixes")
remove(@onNull ArrayList<AudioMix> mixes)257     protected void remove(@NonNull ArrayList<AudioMix> mixes) {
258         for (AudioMix mix : mixes) {
259             mMixes.remove(mix);
260         }
261     }
262 
263     /**
264      * Update audio mixing rules for already registered {@link AudioMix}-es.
265      *
266      * @param audioMixingRuleUpdates {@link List} of {@link Pair}-s containing {@link AudioMix} to
267      *                                           be updated and the new {@link AudioMixingRule}.
268      */
updateMixingRules( @onNull List<Pair<AudioMix, AudioMixingRule>> audioMixingRuleUpdates)269     public void updateMixingRules(
270             @NonNull List<Pair<AudioMix, AudioMixingRule>> audioMixingRuleUpdates) {
271         Objects.requireNonNull(audioMixingRuleUpdates).forEach(
272                 update -> updateMixingRule(update.first, update.second));
273     }
274 
updateMixingRule(AudioMix audioMixToUpdate, AudioMixingRule audioMixingRule)275     private void updateMixingRule(AudioMix audioMixToUpdate, AudioMixingRule audioMixingRule) {
276         mMixes.stream().filter(audioMixToUpdate::equals).findAny().ifPresent(
277                 mix -> mix.setAudioMixingRule(audioMixingRule));
278     }
279 
mixTypeId(int type)280     private static String mixTypeId(int type) {
281         if (type == AudioMix.MIX_TYPE_PLAYERS) return "p";
282         else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r";
283         else return "i";
284     }
285 
getRegistration()286     protected String getRegistration() {
287         return mRegistrationId;
288     }
289 }
290