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 
28 import java.util.ArrayList;
29 import java.util.Objects;
30 
31 /**
32  * @hide
33  * Internal storage class for AudioPolicy configuration.
34  */
35 public class AudioPolicyConfig implements Parcelable {
36 
37     private static final String TAG = "AudioPolicyConfig";
38 
39     protected final ArrayList<AudioMix> mMixes;
40     protected int mDuckingPolicy = AudioPolicy.FOCUS_POLICY_DUCKING_IN_APP;
41 
42     private String mRegistrationId = null;
43 
44     /** counter for the mixes that are / have been in the list of AudioMix
45      *  e.g. register 4 mixes (counter is 3), remove 1 (counter is 3), add 1 (counter is 4)
46      */
47     private int mMixCounter = 0;
48 
AudioPolicyConfig(AudioPolicyConfig conf)49     protected AudioPolicyConfig(AudioPolicyConfig conf) {
50         mMixes = conf.mMixes;
51     }
52 
AudioPolicyConfig(ArrayList<AudioMix> mixes)53     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().getTargetMixType());
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.setTargetMixType(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().getTargetMixType() + "\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                     default:
221                         textDump += "invalid rule!";
222                 }
223                 textDump += "\n";
224             }
225         }
226         return textDump;
227     }
228 
229     /**
230      * Very short dump of configuration
231      * @return a condensed dump of configuration, uniquely identifies a policy in a log
232      */
toCompactLogString()233     public String toCompactLogString() {
234         String compactDump = "reg:" + mRegistrationId;
235         int mixNum = 0;
236         for (AudioMix mix : mMixes) {
237             compactDump += " Mix:" + mixNum + "-Typ:" + mixTypePrefix(mix.getMixType())
238                     + "-Rul:" + mix.getRule().getCriteria().size();
239             mixNum++;
240         }
241         return compactDump;
242     }
243 
mixTypePrefix(int mixType)244     private static String mixTypePrefix(int mixType) {
245         switch (mixType) {
246             case AudioMix.MIX_TYPE_PLAYERS:
247                 return "p";
248             case AudioMix.MIX_TYPE_RECORDERS:
249                 return "r";
250             case AudioMix.MIX_TYPE_INVALID:
251             default:
252                 return "#";
253 
254         }
255     }
256 
reset()257     protected void reset() {
258         mMixCounter = 0;
259     }
260 
setRegistration(String regId)261     protected void setRegistration(String regId) {
262         final boolean currentRegNull = (mRegistrationId == null) || mRegistrationId.isEmpty();
263         final boolean newRegNull = (regId == null) || regId.isEmpty();
264         if (!currentRegNull && !newRegNull && !mRegistrationId.equals(regId)) {
265             Log.e(TAG, "Invalid registration transition from " + mRegistrationId + " to " + regId);
266             return;
267         }
268         mRegistrationId = regId == null ? "" : regId;
269         for (AudioMix mix : mMixes) {
270             setMixRegistration(mix);
271         }
272     }
273 
setMixRegistration(@onNull final AudioMix mix)274     private void setMixRegistration(@NonNull final AudioMix mix) {
275         if (!mRegistrationId.isEmpty()) {
276             if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) ==
277                     AudioMix.ROUTE_FLAG_LOOP_BACK) {
278                 mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
279                         + mMixCounter);
280             } else if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_RENDER) ==
281                     AudioMix.ROUTE_FLAG_RENDER) {
282                 mix.setRegistration(mix.mDeviceAddress);
283             }
284         } else {
285             mix.setRegistration("");
286         }
287         mMixCounter++;
288     }
289 
290     @GuardedBy("mMixes")
add(@onNull ArrayList<AudioMix> mixes)291     protected void add(@NonNull ArrayList<AudioMix> mixes) {
292         for (AudioMix mix : mixes) {
293             setMixRegistration(mix);
294             mMixes.add(mix);
295         }
296     }
297 
298     @GuardedBy("mMixes")
remove(@onNull ArrayList<AudioMix> mixes)299     protected void remove(@NonNull ArrayList<AudioMix> mixes) {
300         for (AudioMix mix : mixes) {
301             mMixes.remove(mix);
302         }
303     }
304 
mixTypeId(int type)305     private static String mixTypeId(int type) {
306         if (type == AudioMix.MIX_TYPE_PLAYERS) return "p";
307         else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r";
308         else return "i";
309     }
310 
getRegistration()311     protected String getRegistration() {
312         return mRegistrationId;
313     }
314 }
315