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
17package android.media.audiopolicy;
18
19import android.media.AudioFormat;
20import android.media.AudioManager;
21import android.media.AudioPatch;
22import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
23import android.os.Parcel;
24import android.os.Parcelable;
25import android.util.Log;
26
27import java.util.ArrayList;
28import java.util.Objects;
29
30/**
31 * @hide
32 * Internal storage class for AudioPolicy configuration.
33 */
34public class AudioPolicyConfig implements Parcelable {
35
36    private static final String TAG = "AudioPolicyConfig";
37
38    protected ArrayList<AudioMix> mMixes;
39    protected int mDuckingPolicy = AudioPolicy.FOCUS_POLICY_DUCKING_IN_APP;
40
41    private String mRegistrationId = null;
42
43    protected AudioPolicyConfig(AudioPolicyConfig conf) {
44        mMixes = conf.mMixes;
45    }
46
47    AudioPolicyConfig(ArrayList<AudioMix> mixes) {
48        mMixes = mixes;
49    }
50
51    /**
52     * Add an {@link AudioMix} to be part of the audio policy being built.
53     * @param mix a non-null {@link AudioMix} to be part of the audio policy.
54     * @return the same Builder instance.
55     * @throws IllegalArgumentException
56     */
57    public void addMix(AudioMix mix) throws IllegalArgumentException {
58        if (mix == null) {
59            throw new IllegalArgumentException("Illegal null AudioMix argument");
60        }
61        mMixes.add(mix);
62    }
63
64    public ArrayList<AudioMix> getMixes() {
65        return mMixes;
66    }
67
68    @Override
69    public int hashCode() {
70        return Objects.hash(mMixes);
71    }
72
73    @Override
74    public int describeContents() {
75        return 0;
76    }
77
78    @Override
79    public void writeToParcel(Parcel dest, int flags) {
80        dest.writeInt(mMixes.size());
81        for (AudioMix mix : mMixes) {
82            // write mix route flags
83            dest.writeInt(mix.getRouteFlags());
84            // write callback flags
85            dest.writeInt(mix.mCallbackFlags);
86            // write device information
87            dest.writeInt(mix.mDeviceSystemType);
88            dest.writeString(mix.mDeviceAddress);
89            // write mix format
90            dest.writeInt(mix.getFormat().getSampleRate());
91            dest.writeInt(mix.getFormat().getEncoding());
92            dest.writeInt(mix.getFormat().getChannelMask());
93            // write mix rules
94            final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
95            dest.writeInt(criteria.size());
96            for (AudioMixMatchCriterion criterion : criteria) {
97                criterion.writeToParcel(dest);
98            }
99        }
100    }
101
102    private AudioPolicyConfig(Parcel in) {
103        mMixes = new ArrayList<AudioMix>();
104        int nbMixes = in.readInt();
105        for (int i = 0 ; i < nbMixes ; i++) {
106            final AudioMix.Builder mixBuilder = new AudioMix.Builder();
107            // read mix route flags
108            int routeFlags = in.readInt();
109            mixBuilder.setRouteFlags(routeFlags);
110            // read callback flags
111            mixBuilder.setCallbackFlags(in.readInt());
112            // read device information
113            mixBuilder.setDevice(in.readInt(), in.readString());
114            // read mix format
115            int sampleRate = in.readInt();
116            int encoding = in.readInt();
117            int channelMask = in.readInt();
118            final AudioFormat format = new AudioFormat.Builder().setSampleRate(sampleRate)
119                    .setChannelMask(channelMask).setEncoding(encoding).build();
120            mixBuilder.setFormat(format);
121            // read mix rules
122            int nbRules = in.readInt();
123            AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
124            for (int j = 0 ; j < nbRules ; j++) {
125                // read the matching rules
126                ruleBuilder.addRuleFromParcel(in);
127            }
128            mixBuilder.setMixingRule(ruleBuilder.build());
129            mMixes.add(mixBuilder.build());
130        }
131    }
132
133    public static final Parcelable.Creator<AudioPolicyConfig> CREATOR
134            = new Parcelable.Creator<AudioPolicyConfig>() {
135        /**
136         * Rebuilds an AudioPolicyConfig previously stored with writeToParcel().
137         * @param p Parcel object to read the AudioPolicyConfig from
138         * @return a new AudioPolicyConfig created from the data in the parcel
139         */
140        public AudioPolicyConfig createFromParcel(Parcel p) {
141            return new AudioPolicyConfig(p);
142        }
143        public AudioPolicyConfig[] newArray(int size) {
144            return new AudioPolicyConfig[size];
145        }
146    };
147
148    public String toLogFriendlyString () {
149        String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n");
150        textDump += mMixes.size() + " AudioMix: "+ mRegistrationId + "\n";
151        for(AudioMix mix : mMixes) {
152            // write mix route flags
153            textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n";
154            // write mix format
155            textDump += "  rate=" + mix.getFormat().getSampleRate() + "Hz\n";
156            textDump += "  encoding=" + mix.getFormat().getEncoding() + "\n";
157            textDump += "  channels=0x";
158            textDump += Integer.toHexString(mix.getFormat().getChannelMask()).toUpperCase() +"\n";
159            // write mix rules
160            final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
161            for (AudioMixMatchCriterion criterion : criteria) {
162                switch(criterion.mRule) {
163                    case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE:
164                        textDump += "  exclude usage ";
165                        textDump += criterion.mAttr.usageToString();
166                        break;
167                    case AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE:
168                        textDump += "  match usage ";
169                        textDump += criterion.mAttr.usageToString();
170                        break;
171                    case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
172                        textDump += "  exclude capture preset ";
173                        textDump += criterion.mAttr.getCapturePreset();
174                        break;
175                    case AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
176                        textDump += "  match capture preset ";
177                        textDump += criterion.mAttr.getCapturePreset();
178                        break;
179                    case AudioMixingRule.RULE_MATCH_UID:
180                        textDump += "  match UID ";
181                        textDump += criterion.mIntProp;
182                        break;
183                    case AudioMixingRule.RULE_EXCLUDE_UID:
184                        textDump += "  exclude UID ";
185                        textDump += criterion.mIntProp;
186                        break;
187                    default:
188                        textDump += "invalid rule!";
189                }
190                textDump += "\n";
191            }
192        }
193        return textDump;
194    }
195
196    protected void setRegistration(String regId) {
197        final boolean currentRegNull = (mRegistrationId == null) || mRegistrationId.isEmpty();
198        final boolean newRegNull = (regId == null) || regId.isEmpty();
199        if (!currentRegNull && !newRegNull && !mRegistrationId.equals(regId)) {
200            Log.e(TAG, "Invalid registration transition from " + mRegistrationId + " to " + regId);
201            return;
202        }
203        mRegistrationId = regId == null ? "" : regId;
204        int mixIndex = 0;
205        for (AudioMix mix : mMixes) {
206            if (!mRegistrationId.isEmpty()) {
207                if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) ==
208                        AudioMix.ROUTE_FLAG_LOOP_BACK) {
209                    mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
210                            + mixIndex++);
211                } else if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_RENDER) ==
212                        AudioMix.ROUTE_FLAG_RENDER) {
213                    mix.setRegistration(mix.mDeviceAddress);
214                }
215            } else {
216                mix.setRegistration("");
217            }
218        }
219    }
220
221    private static String mixTypeId(int type) {
222        if (type == AudioMix.MIX_TYPE_PLAYERS) return "p";
223        else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r";
224        else return "i";
225    }
226
227    protected String getRegistration() {
228        return mRegistrationId;
229    }
230}
231