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().allowPrivilegedPlaybackCapture());
101             // write mix rules
102             final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
103             dest.writeInt(criteria.size());
104             for (AudioMixMatchCriterion criterion : criteria) {
105                 criterion.writeToParcel(dest);
106             }
107         }
108     }
109 
AudioPolicyConfig(Parcel in)110     private AudioPolicyConfig(Parcel in) {
111         mMixes = new ArrayList<AudioMix>();
112         int nbMixes = in.readInt();
113         for (int i = 0 ; i < nbMixes ; i++) {
114             final AudioMix.Builder mixBuilder = new AudioMix.Builder();
115             // read mix route flags
116             int routeFlags = in.readInt();
117             mixBuilder.setRouteFlags(routeFlags);
118             // read callback flags
119             mixBuilder.setCallbackFlags(in.readInt());
120             // read device information
121             mixBuilder.setDevice(in.readInt(), in.readString());
122             // read mix format
123             int sampleRate = in.readInt();
124             int encoding = in.readInt();
125             int channelMask = in.readInt();
126             final AudioFormat format = new AudioFormat.Builder().setSampleRate(sampleRate)
127                     .setChannelMask(channelMask).setEncoding(encoding).build();
128             mixBuilder.setFormat(format);
129 
130             AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
131             // write opt-out respect
132             ruleBuilder.allowPrivilegedPlaybackCapture(in.readBoolean());
133             // read mix rules
134             int nbRules = in.readInt();
135             for (int j = 0 ; j < nbRules ; j++) {
136                 // read the matching rules
137                 ruleBuilder.addRuleFromParcel(in);
138             }
139             mixBuilder.setMixingRule(ruleBuilder.build());
140             mMixes.add(mixBuilder.build());
141         }
142     }
143 
144     public static final @android.annotation.NonNull Parcelable.Creator<AudioPolicyConfig> CREATOR
145             = new Parcelable.Creator<AudioPolicyConfig>() {
146         /**
147          * Rebuilds an AudioPolicyConfig previously stored with writeToParcel().
148          * @param p Parcel object to read the AudioPolicyConfig from
149          * @return a new AudioPolicyConfig created from the data in the parcel
150          */
151         public AudioPolicyConfig createFromParcel(Parcel p) {
152             return new AudioPolicyConfig(p);
153         }
154         public AudioPolicyConfig[] newArray(int size) {
155             return new AudioPolicyConfig[size];
156         }
157     };
158 
toLogFriendlyString()159     public String toLogFriendlyString () {
160         String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n");
161         textDump += mMixes.size() + " AudioMix: "+ mRegistrationId + "\n";
162         for(AudioMix mix : mMixes) {
163             // write mix route flags
164             textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n";
165             // write mix format
166             textDump += "  rate=" + mix.getFormat().getSampleRate() + "Hz\n";
167             textDump += "  encoding=" + mix.getFormat().getEncoding() + "\n";
168             textDump += "  channels=0x";
169             textDump += Integer.toHexString(mix.getFormat().getChannelMask()).toUpperCase() + "\n";
170             textDump += "  ignore playback capture opt out="
171                     + mix.getRule().allowPrivilegedPlaybackCapture() + "\n";
172             // write mix rules
173             final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
174             for (AudioMixMatchCriterion criterion : criteria) {
175                 switch(criterion.mRule) {
176                     case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE:
177                         textDump += "  exclude usage ";
178                         textDump += criterion.mAttr.usageToString();
179                         break;
180                     case AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE:
181                         textDump += "  match usage ";
182                         textDump += criterion.mAttr.usageToString();
183                         break;
184                     case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
185                         textDump += "  exclude capture preset ";
186                         textDump += criterion.mAttr.getCapturePreset();
187                         break;
188                     case AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
189                         textDump += "  match capture preset ";
190                         textDump += criterion.mAttr.getCapturePreset();
191                         break;
192                     case AudioMixingRule.RULE_MATCH_UID:
193                         textDump += "  match UID ";
194                         textDump += criterion.mIntProp;
195                         break;
196                     case AudioMixingRule.RULE_EXCLUDE_UID:
197                         textDump += "  exclude UID ";
198                         textDump += criterion.mIntProp;
199                         break;
200                     default:
201                         textDump += "invalid rule!";
202                 }
203                 textDump += "\n";
204             }
205         }
206         return textDump;
207     }
208 
setRegistration(String regId)209     protected void setRegistration(String regId) {
210         final boolean currentRegNull = (mRegistrationId == null) || mRegistrationId.isEmpty();
211         final boolean newRegNull = (regId == null) || regId.isEmpty();
212         if (!currentRegNull && !newRegNull && !mRegistrationId.equals(regId)) {
213             Log.e(TAG, "Invalid registration transition from " + mRegistrationId + " to " + regId);
214             return;
215         }
216         mRegistrationId = regId == null ? "" : regId;
217         for (AudioMix mix : mMixes) {
218             setMixRegistration(mix);
219         }
220     }
221 
setMixRegistration(@onNull final AudioMix mix)222     private void setMixRegistration(@NonNull final AudioMix mix) {
223         if (!mRegistrationId.isEmpty()) {
224             if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) ==
225                     AudioMix.ROUTE_FLAG_LOOP_BACK) {
226                 mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
227                         + mMixCounter);
228             } else if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_RENDER) ==
229                     AudioMix.ROUTE_FLAG_RENDER) {
230                 mix.setRegistration(mix.mDeviceAddress);
231             }
232         } else {
233             mix.setRegistration("");
234         }
235         mMixCounter++;
236     }
237 
238     @GuardedBy("mMixes")
add(@onNull ArrayList<AudioMix> mixes)239     protected void add(@NonNull ArrayList<AudioMix> mixes) {
240         for (AudioMix mix : mixes) {
241             setMixRegistration(mix);
242             mMixes.add(mix);
243         }
244     }
245 
246     @GuardedBy("mMixes")
remove(@onNull ArrayList<AudioMix> mixes)247     protected void remove(@NonNull ArrayList<AudioMix> mixes) {
248         for (AudioMix mix : mixes) {
249             mMixes.remove(mix);
250         }
251     }
252 
mixTypeId(int type)253     private static String mixTypeId(int type) {
254         if (type == AudioMix.MIX_TYPE_PLAYERS) return "p";
255         else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r";
256         else return "i";
257     }
258 
getRegistration()259     protected String getRegistration() {
260         return mRegistrationId;
261     }
262 }
263