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.annotation.NonNull;
20import android.media.AudioFormat;
21import android.media.AudioManager;
22import android.media.AudioPatch;
23import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
24import android.os.Parcel;
25import android.os.Parcelable;
26import android.util.Log;
27
28import com.android.internal.annotations.GuardedBy;
29
30import java.util.ArrayList;
31import java.util.Objects;
32
33/**
34 * @hide
35 * Internal storage class for AudioPolicy configuration.
36 */
37public 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
51    protected AudioPolicyConfig(AudioPolicyConfig conf) {
52        mMixes = conf.mMixes;
53    }
54
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     */
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
72    public ArrayList<AudioMix> getMixes() {
73        return mMixes;
74    }
75
76    @Override
77    public int hashCode() {
78        return Objects.hash(mMixes);
79    }
80
81    @Override
82    public int describeContents() {
83        return 0;
84    }
85
86    @Override
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
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
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
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
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")
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")
242    protected void remove(@NonNull ArrayList<AudioMix> mixes) {
243        for (AudioMix mix : mixes) {
244            mMixes.remove(mix);
245        }
246    }
247
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
254    protected String getRegistration() {
255        return mRegistrationId;
256    }
257}
258