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.AudioManager;
22 import android.media.AudioPatch;
23 import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.util.Log;
27 
28 import com.android.internal.annotations.GuardedBy;
29 
30 import java.util.ArrayList;
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     /** counter for the mixes that are / have been in the list of AudioMix
47      *  e.g. register 4 mixes (counter is 3), remove 1 (counter is 3), add 1 (counter is 4)
48      */
49     private int mMixCounter = 0;
50 
AudioPolicyConfig(AudioPolicyConfig conf)51     protected AudioPolicyConfig(AudioPolicyConfig conf) {
52         mMixes = conf.mMixes;
53     }
54 
AudioPolicyConfig(ArrayList<AudioMix> mixes)55     AudioPolicyConfig(ArrayList<AudioMix> mixes) {
56         mMixes = mixes;
57     }
58 
59     /**
60      * Add an {@link AudioMix} to be part of the audio policy being built.
61      * @param mix a non-null {@link AudioMix} to be part of the audio policy.
62      * @return the same Builder instance.
63      * @throws IllegalArgumentException
64      */
addMix(AudioMix mix)65     public void addMix(AudioMix mix) throws IllegalArgumentException {
66         if (mix == null) {
67             throw new IllegalArgumentException("Illegal null AudioMix argument");
68         }
69         mMixes.add(mix);
70     }
71 
getMixes()72     public ArrayList<AudioMix> getMixes() {
73         return mMixes;
74     }
75 
76     @Override
hashCode()77     public int hashCode() {
78         return Objects.hash(mMixes);
79     }
80 
81     @Override
describeContents()82     public int describeContents() {
83         return 0;
84     }
85 
86     @Override
writeToParcel(Parcel dest, int flags)87     public void writeToParcel(Parcel dest, int flags) {
88         dest.writeInt(mMixes.size());
89         for (AudioMix mix : mMixes) {
90             // write mix route flags
91             dest.writeInt(mix.getRouteFlags());
92             // write callback flags
93             dest.writeInt(mix.mCallbackFlags);
94             // write device information
95             dest.writeInt(mix.mDeviceSystemType);
96             dest.writeString(mix.mDeviceAddress);
97             // write mix format
98             dest.writeInt(mix.getFormat().getSampleRate());
99             dest.writeInt(mix.getFormat().getEncoding());
100             dest.writeInt(mix.getFormat().getChannelMask());
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             // read mix rules
130             int nbRules = in.readInt();
131             AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
132             for (int j = 0 ; j < nbRules ; j++) {
133                 // read the matching rules
134                 ruleBuilder.addRuleFromParcel(in);
135             }
136             mixBuilder.setMixingRule(ruleBuilder.build());
137             mMixes.add(mixBuilder.build());
138         }
139     }
140 
141     public static final Parcelable.Creator<AudioPolicyConfig> CREATOR
142             = new Parcelable.Creator<AudioPolicyConfig>() {
143         /**
144          * Rebuilds an AudioPolicyConfig previously stored with writeToParcel().
145          * @param p Parcel object to read the AudioPolicyConfig from
146          * @return a new AudioPolicyConfig created from the data in the parcel
147          */
148         public AudioPolicyConfig createFromParcel(Parcel p) {
149             return new AudioPolicyConfig(p);
150         }
151         public AudioPolicyConfig[] newArray(int size) {
152             return new AudioPolicyConfig[size];
153         }
154     };
155 
toLogFriendlyString()156     public String toLogFriendlyString () {
157         String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n");
158         textDump += mMixes.size() + " AudioMix: "+ mRegistrationId + "\n";
159         for(AudioMix mix : mMixes) {
160             // write mix route flags
161             textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n";
162             // write mix format
163             textDump += "  rate=" + mix.getFormat().getSampleRate() + "Hz\n";
164             textDump += "  encoding=" + mix.getFormat().getEncoding() + "\n";
165             textDump += "  channels=0x";
166             textDump += Integer.toHexString(mix.getFormat().getChannelMask()).toUpperCase() +"\n";
167             // write mix rules
168             final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
169             for (AudioMixMatchCriterion criterion : criteria) {
170                 switch(criterion.mRule) {
171                     case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE:
172                         textDump += "  exclude usage ";
173                         textDump += criterion.mAttr.usageToString();
174                         break;
175                     case AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE:
176                         textDump += "  match usage ";
177                         textDump += criterion.mAttr.usageToString();
178                         break;
179                     case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
180                         textDump += "  exclude capture preset ";
181                         textDump += criterion.mAttr.getCapturePreset();
182                         break;
183                     case AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
184                         textDump += "  match capture preset ";
185                         textDump += criterion.mAttr.getCapturePreset();
186                         break;
187                     case AudioMixingRule.RULE_MATCH_UID:
188                         textDump += "  match UID ";
189                         textDump += criterion.mIntProp;
190                         break;
191                     case AudioMixingRule.RULE_EXCLUDE_UID:
192                         textDump += "  exclude UID ";
193                         textDump += criterion.mIntProp;
194                         break;
195                     default:
196                         textDump += "invalid rule!";
197                 }
198                 textDump += "\n";
199             }
200         }
201         return textDump;
202     }
203 
setRegistration(String regId)204     protected void setRegistration(String regId) {
205         final boolean currentRegNull = (mRegistrationId == null) || mRegistrationId.isEmpty();
206         final boolean newRegNull = (regId == null) || regId.isEmpty();
207         if (!currentRegNull && !newRegNull && !mRegistrationId.equals(regId)) {
208             Log.e(TAG, "Invalid registration transition from " + mRegistrationId + " to " + regId);
209             return;
210         }
211         mRegistrationId = regId == null ? "" : regId;
212         for (AudioMix mix : mMixes) {
213             setMixRegistration(mix);
214         }
215     }
216 
setMixRegistration(@onNull final AudioMix mix)217     private void setMixRegistration(@NonNull final AudioMix mix) {
218         if (!mRegistrationId.isEmpty()) {
219             if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) ==
220                     AudioMix.ROUTE_FLAG_LOOP_BACK) {
221                 mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
222                         + mMixCounter);
223             } else if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_RENDER) ==
224                     AudioMix.ROUTE_FLAG_RENDER) {
225                 mix.setRegistration(mix.mDeviceAddress);
226             }
227         } else {
228             mix.setRegistration("");
229         }
230         mMixCounter++;
231     }
232 
233     @GuardedBy("mMixes")
add(@onNull ArrayList<AudioMix> mixes)234     protected void add(@NonNull ArrayList<AudioMix> mixes) {
235         for (AudioMix mix : mixes) {
236             setMixRegistration(mix);
237             mMixes.add(mix);
238         }
239     }
240 
241     @GuardedBy("mMixes")
remove(@onNull ArrayList<AudioMix> mixes)242     protected void remove(@NonNull ArrayList<AudioMix> mixes) {
243         for (AudioMix mix : mixes) {
244             mMixes.remove(mix);
245         }
246     }
247 
mixTypeId(int type)248     private static String mixTypeId(int type) {
249         if (type == AudioMix.MIX_TYPE_PLAYERS) return "p";
250         else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r";
251         else return "i";
252     }
253 
getRegistration()254     protected String getRegistration() {
255         return mRegistrationId;
256     }
257 }
258