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