AudioPolicy.java revision 0212be5150fb9fb3c340f3c7e51f6126372cc6f9
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.AudioFocusInfo;
26import android.media.AudioFormat;
27import android.media.AudioManager;
28import android.media.AudioRecord;
29import android.media.AudioTrack;
30import android.media.IAudioService;
31import android.media.MediaRecorder;
32import android.os.Binder;
33import android.os.Handler;
34import android.os.IBinder;
35import android.os.Looper;
36import android.os.Message;
37import android.os.RemoteException;
38import android.os.ServiceManager;
39import android.util.Log;
40import android.util.Slog;
41
42import java.lang.annotation.Retention;
43import java.lang.annotation.RetentionPolicy;
44import java.util.ArrayList;
45
46/**
47 * @hide
48 * AudioPolicy provides access to the management of audio routing and audio focus.
49 */
50@SystemApi
51public class AudioPolicy {
52
53    private static final String TAG = "AudioPolicy";
54    private static final boolean DEBUG = false;
55    private final Object mLock = new Object();
56
57    /**
58     * The status of an audio policy that is valid but cannot be used because it is not registered.
59     */
60    @SystemApi
61    public static final int POLICY_STATUS_UNREGISTERED = 1;
62    /**
63     * The status of an audio policy that is valid, successfully registered and thus active.
64     */
65    @SystemApi
66    public static final int POLICY_STATUS_REGISTERED = 2;
67
68    private int mStatus;
69    private String mRegistrationId;
70    private AudioPolicyStatusListener mStatusListener;
71
72    /**
73     * The behavior of a policy with regards to audio focus where it relies on the application
74     * to do the ducking, the is the legacy and default behavior.
75     */
76    @SystemApi
77    public static final int FOCUS_POLICY_DUCKING_IN_APP = 0;
78    public static final int FOCUS_POLICY_DUCKING_DEFAULT = FOCUS_POLICY_DUCKING_IN_APP;
79    /**
80     * The behavior of a policy with regards to audio focus where it handles ducking instead
81     * of the application losing focus and being signaled it can duck (as communicated by
82     * {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}).
83     * <br>Can only be used after having set a listener with
84     * {@link AudioPolicy#setAudioPolicyFocusListener(AudioPolicyFocusListener)}.
85     */
86    @SystemApi
87    public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1;
88
89    private AudioPolicyFocusListener mFocusListener;
90
91    private Context mContext;
92
93    private AudioPolicyConfig mConfig;
94
95    /** @hide */
96    public AudioPolicyConfig getConfig() { return mConfig; }
97    /** @hide */
98    public boolean hasFocusListener() { return mFocusListener != null; }
99
100    /**
101     * The parameter is guaranteed non-null through the Builder
102     */
103    private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper,
104            AudioPolicyFocusListener fl, AudioPolicyStatusListener sl) {
105        mConfig = config;
106        mStatus = POLICY_STATUS_UNREGISTERED;
107        mContext = context;
108        if (looper == null) {
109            looper = Looper.getMainLooper();
110        }
111        if (looper != null) {
112            mEventHandler = new EventHandler(this, looper);
113        } else {
114            mEventHandler = null;
115            Log.e(TAG, "No event handler due to looper without a thread");
116        }
117        mFocusListener = fl;
118        mStatusListener = sl;
119    }
120
121    /**
122     * Builder class for {@link AudioPolicy} objects
123     */
124    @SystemApi
125    public static class Builder {
126        private ArrayList<AudioMix> mMixes;
127        private Context mContext;
128        private Looper mLooper;
129        private AudioPolicyFocusListener mFocusListener;
130        private AudioPolicyStatusListener mStatusListener;
131
132        /**
133         * Constructs a new Builder with no audio mixes.
134         * @param context the context for the policy
135         */
136        @SystemApi
137        public Builder(Context context) {
138            mMixes = new ArrayList<AudioMix>();
139            mContext = context;
140        }
141
142        /**
143         * Add an {@link AudioMix} to be part of the audio policy being built.
144         * @param mix a non-null {@link AudioMix} to be part of the audio policy.
145         * @return the same Builder instance.
146         * @throws IllegalArgumentException
147         */
148        @SystemApi
149        public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
150            if (mix == null) {
151                throw new IllegalArgumentException("Illegal null AudioMix argument");
152            }
153            mMixes.add(mix);
154            return this;
155        }
156
157        /**
158         * Sets the {@link Looper} on which to run the event loop.
159         * @param looper a non-null specific Looper.
160         * @return the same Builder instance.
161         * @throws IllegalArgumentException
162         */
163        @SystemApi
164        public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
165            if (looper == null) {
166                throw new IllegalArgumentException("Illegal null Looper argument");
167            }
168            mLooper = looper;
169            return this;
170        }
171
172        /**
173         * Sets the audio focus listener for the policy.
174         * @param l a {@link AudioPolicy.AudioPolicyFocusListener}
175         */
176        @SystemApi
177        public void setAudioPolicyFocusListener(AudioPolicyFocusListener l) {
178            mFocusListener = l;
179        }
180
181        /**
182         * Sets the audio policy status listener.
183         * @param l a {@link AudioPolicy.AudioPolicyStatusListener}
184         */
185        @SystemApi
186        public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
187            mStatusListener = l;
188        }
189
190        @SystemApi
191        public AudioPolicy build() {
192            return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper,
193                    mFocusListener, mStatusListener);
194        }
195    }
196
197    public void setRegistration(String regId) {
198        synchronized (mLock) {
199            mRegistrationId = regId;
200            mConfig.setRegistration(regId);
201            if (regId != null) {
202                mStatus = POLICY_STATUS_REGISTERED;
203            } else {
204                mStatus = POLICY_STATUS_UNREGISTERED;
205            }
206        }
207        sendMsg(MSG_POLICY_STATUS_CHANGE);
208    }
209
210    private boolean policyReadyToUse() {
211        synchronized (mLock) {
212            if (mStatus != POLICY_STATUS_REGISTERED) {
213                Log.e(TAG, "Cannot use unregistered AudioPolicy");
214                return false;
215            }
216            if (mContext == null) {
217                Log.e(TAG, "Cannot use AudioPolicy without context");
218                return false;
219            }
220            if (mRegistrationId == null) {
221                Log.e(TAG, "Cannot use unregistered AudioPolicy");
222                return false;
223            }
224        }
225        if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
226                        android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
227            Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid "
228                    + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING");
229            return false;
230        }
231        return true;
232    }
233
234    private void checkMixReadyToUse(AudioMix mix, boolean forTrack)
235            throws IllegalArgumentException{
236        if (mix == null) {
237            String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation"
238                    : "Invalid null AudioMix for AudioRecord creation";
239            throw new IllegalArgumentException(msg);
240        }
241        if (!mConfig.mMixes.contains(mix)) {
242            throw new IllegalArgumentException("Invalid mix: not part of this policy");
243        }
244        if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK)
245        {
246            throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back");
247        }
248        if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) {
249            throw new IllegalArgumentException(
250                    "Invalid AudioMix: not defined for being a recording source");
251        }
252        if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) {
253            throw new IllegalArgumentException(
254                    "Invalid AudioMix: not defined for capturing playback");
255        }
256    }
257
258    /**
259     * Returns the current behavior for audio focus-related ducking.
260     * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
261     */
262    @SystemApi
263    public int getFocusDuckingBehavior() {
264        return mConfig.mDuckingPolicy;
265    }
266
267    // Note on implementation: not part of the Builder as there can be only one registered policy
268    // that handles ducking but there can be multiple policies
269    /**
270     * Sets the behavior for audio focus-related ducking.
271     * There must be a focus listener if this policy is to handle ducking.
272     * @param behavior {@link #FOCUS_POLICY_DUCKING_IN_APP} or
273     *     {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
274     * @return {@link AudioManager#SUCCESS} or {@link AudioManager#ERROR} (for instance if there
275     *     is already an audio policy that handles ducking).
276     * @throws IllegalArgumentException
277     * @throws IllegalStateException
278     */
279    @SystemApi
280    public int setFocusDuckingBehavior(int behavior)
281            throws IllegalArgumentException, IllegalStateException {
282        if ((behavior != FOCUS_POLICY_DUCKING_IN_APP)
283                && (behavior != FOCUS_POLICY_DUCKING_IN_POLICY)) {
284            throw new IllegalArgumentException("Invalid ducking behavior " + behavior);
285        }
286        synchronized (mLock) {
287            if (mStatus != POLICY_STATUS_REGISTERED) {
288                throw new IllegalStateException(
289                        "Cannot change ducking behavior for unregistered policy");
290            }
291            if ((behavior == FOCUS_POLICY_DUCKING_IN_POLICY)
292                    && (mFocusListener == null)) {
293                // there must be a focus listener if the policy handles ducking
294                throw new IllegalStateException(
295                        "Cannot handle ducking without an audio focus listener");
296            }
297            IAudioService service = getService();
298            try {
299                final int status = service.setFocusPropertiesForPolicy(behavior /*duckingBehavior*/,
300                        this.cb());
301                if (status == AudioManager.SUCCESS) {
302                    mConfig.mDuckingPolicy = behavior;
303                }
304                return status;
305            } catch (RemoteException e) {
306                Log.e(TAG, "Dead object in setFocusPropertiesForPolicy for behavior", e);
307                return AudioManager.ERROR;
308            }
309        }
310    }
311
312    /**
313     * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
314     * Audio buffers recorded through the created instance will contain the mix of the audio
315     * streams that fed the given mixer.
316     * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
317     *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
318     * @return a new {@link AudioRecord} instance whose data format is the one defined in the
319     *     {@link AudioMix}, or null if this policy was not successfully registered
320     *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
321     * @throws IllegalArgumentException
322     */
323    @SystemApi
324    public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException {
325        if (!policyReadyToUse()) {
326            Log.e(TAG, "Cannot create AudioRecord sink for AudioMix");
327            return null;
328        }
329        checkMixReadyToUse(mix, false/*not for an AudioTrack*/);
330        // create an AudioFormat from the mix format compatible with recording, as the mix
331        // was defined for playback
332        AudioFormat mixFormat = new AudioFormat.Builder(mix.getFormat())
333                .setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask(
334                        mix.getFormat().getChannelMask()))
335                .build();
336        // create the AudioRecord, configured for loop back, using the same format as the mix
337        AudioRecord ar = new AudioRecord(
338                new AudioAttributes.Builder()
339                        .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
340                        .addTag(addressForTag(mix))
341                        .build(),
342                mixFormat,
343                AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
344                        // using stereo for buffer size to avoid the current poor support for masks
345                        AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()),
346                AudioManager.AUDIO_SESSION_ID_GENERATE
347                );
348        return ar;
349    }
350
351    /**
352     * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}.
353     * Audio buffers played through the created instance will be sent to the given mix
354     * to be recorded through the recording APIs.
355     * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
356     *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
357     * @return a new {@link AudioTrack} instance whose data format is the one defined in the
358     *     {@link AudioMix}, or null if this policy was not successfully registered
359     *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
360     * @throws IllegalArgumentException
361     */
362    @SystemApi
363    public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException {
364        if (!policyReadyToUse()) {
365            Log.e(TAG, "Cannot create AudioTrack source for AudioMix");
366            return null;
367        }
368        checkMixReadyToUse(mix, true/*for an AudioTrack*/);
369        // create the AudioTrack, configured for loop back, using the same format as the mix
370        AudioTrack at = new AudioTrack(
371                new AudioAttributes.Builder()
372                        .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
373                        .addTag(addressForTag(mix))
374                        .build(),
375                mix.getFormat(),
376                AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
377                        mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()),
378                AudioTrack.MODE_STREAM,
379                AudioManager.AUDIO_SESSION_ID_GENERATE
380                );
381        return at;
382    }
383
384    @SystemApi
385    public int getStatus() {
386        return mStatus;
387    }
388
389    @SystemApi
390    public static abstract class AudioPolicyStatusListener {
391        public void onStatusChange() {}
392        public void onMixStateUpdate(AudioMix mix) {}
393    }
394
395    @SystemApi
396    public static abstract class AudioPolicyFocusListener {
397        public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {}
398        public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {}
399    }
400
401    private void onPolicyStatusChange() {
402        AudioPolicyStatusListener l;
403        synchronized (mLock) {
404            if (mStatusListener == null) {
405                return;
406            }
407            l = mStatusListener;
408        }
409        l.onStatusChange();
410    }
411
412    //==================================================
413    // Callback interface
414
415    /** @hide */
416    public IAudioPolicyCallback cb() { return mPolicyCb; }
417
418    private final IAudioPolicyCallback mPolicyCb = new IAudioPolicyCallback.Stub() {
419
420        public void notifyAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
421            sendMsg(MSG_FOCUS_GRANT, afi, requestResult);
422            if (DEBUG) {
423                Log.v(TAG, "notifyAudioFocusGrant: pack=" + afi.getPackageName() + " client="
424                        + afi.getClientId() + "reqRes=" + requestResult);
425            }
426        }
427
428        public void notifyAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
429            sendMsg(MSG_FOCUS_LOSS, afi, wasNotified ? 1 : 0);
430            if (DEBUG) {
431                Log.v(TAG, "notifyAudioFocusLoss: pack=" + afi.getPackageName() + " client="
432                        + afi.getClientId() + "wasNotified=" + wasNotified);
433            }
434        }
435    };
436
437    //==================================================
438    // Event handling
439    private final EventHandler mEventHandler;
440    private final static int MSG_POLICY_STATUS_CHANGE = 0;
441    private final static int MSG_FOCUS_GRANT = 1;
442    private final static int MSG_FOCUS_LOSS = 2;
443
444    private class EventHandler extends Handler {
445        public EventHandler(AudioPolicy ap, Looper looper) {
446            super(looper);
447        }
448
449        @Override
450        public void handleMessage(Message msg) {
451            switch(msg.what) {
452                case MSG_POLICY_STATUS_CHANGE:
453                    onPolicyStatusChange();
454                    break;
455                case MSG_FOCUS_GRANT:
456                    if (mFocusListener != null) {
457                        mFocusListener.onAudioFocusGrant(
458                                (AudioFocusInfo) msg.obj, msg.arg1);
459                    }
460                    break;
461                case MSG_FOCUS_LOSS:
462                    if (mFocusListener != null) {
463                        mFocusListener.onAudioFocusLoss(
464                                (AudioFocusInfo) msg.obj, msg.arg1 != 0);
465                    }
466                    break;
467                default:
468                    Log.e(TAG, "Unknown event " + msg.what);
469            }
470        }
471    }
472
473    //==========================================================
474    // Utils
475    private static String addressForTag(AudioMix mix) {
476        return "addr=" + mix.getRegistration();
477    }
478
479    private void sendMsg(int msg) {
480        if (mEventHandler != null) {
481            mEventHandler.sendEmptyMessage(msg);
482        }
483    }
484
485    private void sendMsg(int msg, Object obj, int i) {
486        if (mEventHandler != null) {
487            mEventHandler.sendMessage(
488                    mEventHandler.obtainMessage(msg, i /*arg1*/, 0 /*arg2, ignored*/, obj));
489        }
490    }
491
492    private static IAudioService sService;
493
494    private static IAudioService getService()
495    {
496        if (sService != null) {
497            return sService;
498        }
499        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
500        sService = IAudioService.Stub.asInterface(b);
501        return sService;
502    }
503
504    public String toLogFriendlyString() {
505        String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
506        textDump += "config=" + mConfig.toLogFriendlyString();
507        return (textDump);
508    }
509
510    /** @hide */
511    @IntDef({
512        POLICY_STATUS_REGISTERED,
513        POLICY_STATUS_UNREGISTERED
514    })
515    @Retention(RetentionPolicy.SOURCE)
516    public @interface PolicyStatus {}
517}
518