AudioPolicy.java revision 1b3541d5eedb332ea01066b4a78a2d06d5304044
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.IntDef;
20import android.annotation.NonNull;
21import android.annotation.SystemApi;
22import android.content.Context;
23import android.content.pm.PackageManager;
24import android.media.AudioAttributes;
25import android.media.AudioFormat;
26import android.media.AudioManager;
27import android.media.AudioRecord;
28import android.media.AudioTrack;
29import android.media.MediaRecorder;
30import android.os.Binder;
31import android.os.Handler;
32import android.os.IBinder;
33import android.os.Looper;
34import android.os.Message;
35import android.util.Log;
36import android.util.Slog;
37
38import java.lang.annotation.Retention;
39import java.lang.annotation.RetentionPolicy;
40import java.util.ArrayList;
41
42/**
43 * @hide
44 * AudioPolicy provides access to the management of audio routing and audio focus.
45 */
46@SystemApi
47public class AudioPolicy {
48
49    private static final String TAG = "AudioPolicy";
50
51    /**
52     * The status of an audio policy that is valid but cannot be used because it is not registered.
53     */
54    @SystemApi
55    public static final int POLICY_STATUS_UNREGISTERED = 1;
56    /**
57     * The status of an audio policy that is valid, successfully registered and thus active.
58     */
59    @SystemApi
60    public static final int POLICY_STATUS_REGISTERED = 2;
61
62    private int mStatus;
63    private String mRegistrationId;
64    private AudioPolicyStatusListener mStatusListener;
65
66    private final IBinder mToken = new Binder();
67    /** @hide */
68    public IBinder token() { return mToken; }
69    private Context mContext;
70
71    private AudioPolicyConfig mConfig;
72    /** @hide */
73    public AudioPolicyConfig getConfig() { return mConfig; }
74
75    /**
76     * The parameter is guaranteed non-null through the Builder
77     */
78    private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper) {
79        mConfig = config;
80        mStatus = POLICY_STATUS_UNREGISTERED;
81        mContext = context;
82        if (looper == null) {
83            looper = Looper.getMainLooper();
84        }
85        if (looper != null) {
86            mEventHandler = new EventHandler(this, looper);
87        } else {
88            mEventHandler = null;
89            Log.e(TAG, "No event handler due to looper without a thread");
90        }
91    }
92
93    /**
94     * Builder class for {@link AudioPolicy} objects
95     */
96    @SystemApi
97    public static class Builder {
98        private ArrayList<AudioMix> mMixes;
99        private Context mContext;
100        private Looper mLooper;
101
102        /**
103         * Constructs a new Builder with no audio mixes.
104         * @param context the context for the policy
105         */
106        public Builder(Context context) {
107            mMixes = new ArrayList<AudioMix>();
108            mContext = context;
109        }
110
111        /**
112         * Add an {@link AudioMix} to be part of the audio policy being built.
113         * @param mix a non-null {@link AudioMix} to be part of the audio policy.
114         * @return the same Builder instance.
115         * @throws IllegalArgumentException
116         */
117        public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
118            if (mix == null) {
119                throw new IllegalArgumentException("Illegal null AudioMix argument");
120            }
121            mMixes.add(mix);
122            return this;
123        }
124
125        /**
126         * Sets the {@link Looper} on which to run the event loop.
127         * @param looper a non-null specific Looper.
128         * @return the same Builder instance.
129         * @throws IllegalArgumentException
130         */
131        public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
132            if (looper == null) {
133                throw new IllegalArgumentException("Illegal null Looper argument");
134            }
135            mLooper = looper;
136            return this;
137        }
138
139        public AudioPolicy build() {
140            return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper);
141        }
142    }
143
144    public void setRegistration(String regId) {
145        mRegistrationId = regId;
146        mConfig.setRegistration(regId);
147        if (regId != null) {
148            mStatus = POLICY_STATUS_REGISTERED;
149        } else {
150            mStatus = POLICY_STATUS_UNREGISTERED;
151        }
152        sendMsg(mEventHandler, MSG_POLICY_STATUS_CHANGE);
153    }
154
155    private boolean policyReadyToUse() {
156        if (mStatus != POLICY_STATUS_REGISTERED) {
157            Log.e(TAG, "Cannot use unregistered AudioPolicy");
158            return false;
159        }
160        if (mContext == null) {
161            Log.e(TAG, "Cannot use AudioPolicy without context");
162            return false;
163        }
164        if (mRegistrationId == null) {
165            Log.e(TAG, "Cannot use unregistered AudioPolicy");
166            return false;
167        }
168        if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
169                        android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
170            Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid "
171                    + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING");
172            return false;
173        }
174        return true;
175    }
176
177    private void checkMixReadyToUse(AudioMix mix, boolean forTrack)
178            throws IllegalArgumentException{
179        if (mix == null) {
180            String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation"
181                    : "Invalid null AudioMix for AudioRecord creation";
182            throw new IllegalArgumentException(msg);
183        }
184        if (!mConfig.mMixes.contains(mix)) {
185            throw new IllegalArgumentException("Invalid mix: not part of this policy");
186        }
187        if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK)
188        {
189            throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back");
190        }
191        if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) {
192            throw new IllegalArgumentException(
193                    "Invalid AudioMix: not defined for being a recording source");
194        }
195        if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) {
196            throw new IllegalArgumentException(
197                    "Invalid AudioMix: not defined for capturing playback");
198        }
199    }
200
201    /**
202     * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
203     * Audio buffers recorded through the created instance will contain the mix of the audio
204     * streams that fed the given mixer.
205     * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
206     *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
207     * @return a new {@link AudioRecord} instance whose data format is the one defined in the
208     *     {@link AudioMix}, or null if this policy was not successfully registered
209     *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
210     * @throws IllegalArgumentException
211     */
212    @SystemApi
213    public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException {
214        if (!policyReadyToUse()) {
215            Log.e(TAG, "Cannot create AudioRecord sink for AudioMix");
216            return null;
217        }
218        checkMixReadyToUse(mix, false/*not for an AudioTrack*/);
219        // create an AudioFormat from the mix format compatible with recording, as the mix
220        // was defined for playback
221        AudioFormat mixFormat = new AudioFormat.Builder(mix.getFormat())
222                .setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask(
223                        mix.getFormat().getChannelMask()))
224                .build();
225        // create the AudioRecord, configured for loop back, using the same format as the mix
226        AudioRecord ar = new AudioRecord(
227                new AudioAttributes.Builder()
228                        .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
229                        .addTag(addressForTag(mix))
230                        .build(),
231                mixFormat,
232                AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
233                        // using stereo for buffer size to avoid the current poor support for masks
234                        AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()),
235                AudioManager.AUDIO_SESSION_ID_GENERATE
236                );
237        return ar;
238    }
239
240    /**
241     * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}.
242     * Audio buffers played through the created instance will be sent to the given mix
243     * to be recorded through the recording APIs.
244     * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
245     *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
246     * @return a new {@link AudioTrack} instance whose data format is the one defined in the
247     *     {@link AudioMix}, or null if this policy was not successfully registered
248     *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
249     * @throws IllegalArgumentException
250     */
251    @SystemApi
252    public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException {
253        if (!policyReadyToUse()) {
254            Log.e(TAG, "Cannot create AudioTrack source for AudioMix");
255            return null;
256        }
257        checkMixReadyToUse(mix, true/*for an AudioTrack*/);
258        // create the AudioTrack, configured for loop back, using the same format as the mix
259        AudioTrack at = new AudioTrack(
260                new AudioAttributes.Builder()
261                        .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
262                        .addTag(addressForTag(mix))
263                        .build(),
264                mix.getFormat(),
265                AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
266                        mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()),
267                AudioTrack.MODE_STREAM,
268                AudioManager.AUDIO_SESSION_ID_GENERATE
269                );
270        return at;
271    }
272
273    @SystemApi
274    public int getStatus() {
275        return mStatus;
276    }
277
278    @SystemApi
279    public static abstract class AudioPolicyStatusListener {
280        public void onStatusChange() {}
281        public void onMixStateUpdate(AudioMix mix) {}
282    }
283
284    @SystemApi
285    synchronized public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
286        mStatusListener = l;
287    }
288
289    synchronized private void onPolicyStatusChange() {
290        if (mStatusListener == null) {
291            return;
292        }
293        mStatusListener.onStatusChange();
294    }
295
296    //==================================================
297    // Event handling
298    private final EventHandler mEventHandler;
299    private final static int MSG_POLICY_STATUS_CHANGE = 0;
300
301    private class EventHandler extends Handler {
302        public EventHandler(AudioPolicy ap, Looper looper) {
303            super(looper);
304        }
305
306        @Override
307        public void handleMessage(Message msg) {
308            switch(msg.what) {
309                case MSG_POLICY_STATUS_CHANGE:
310                    onPolicyStatusChange();
311                    break;
312                default:
313                    Log.e(TAG, "Unknown event " + msg.what);
314            }
315        }
316    }
317
318    //==========================================================
319    // Utils
320    private static String addressForTag(AudioMix mix) {
321        return "addr=" + mix.getRegistration();
322    }
323
324    private static void sendMsg(Handler handler, int msg) {
325        if (handler != null) {
326            handler.sendEmptyMessage(msg);
327        }
328    }
329
330    public String toLogFriendlyString() {
331        String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
332        textDump += "config=" + mConfig.toLogFriendlyString();
333        return (textDump);
334    }
335
336    /** @hide */
337    @IntDef({
338        POLICY_STATUS_REGISTERED,
339        POLICY_STATUS_UNREGISTERED
340    })
341    @Retention(RetentionPolicy.SOURCE)
342    public @interface PolicyStatus {}
343}
344