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    private boolean mIsFocusPolicy;
72
73    /**
74     * The behavior of a policy with regards to audio focus where it relies on the application
75     * to do the ducking, the is the legacy and default behavior.
76     */
77    @SystemApi
78    public static final int FOCUS_POLICY_DUCKING_IN_APP = 0;
79    public static final int FOCUS_POLICY_DUCKING_DEFAULT = FOCUS_POLICY_DUCKING_IN_APP;
80    /**
81     * The behavior of a policy with regards to audio focus where it handles ducking instead
82     * of the application losing focus and being signaled it can duck (as communicated by
83     * {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}).
84     * <br>Can only be used after having set a listener with
85     * {@link AudioPolicy#setAudioPolicyFocusListener(AudioPolicyFocusListener)}.
86     */
87    @SystemApi
88    public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1;
89
90    private AudioPolicyFocusListener mFocusListener;
91
92    private Context mContext;
93
94    private AudioPolicyConfig mConfig;
95
96    /** @hide */
97    public AudioPolicyConfig getConfig() { return mConfig; }
98    /** @hide */
99    public boolean hasFocusListener() { return mFocusListener != null; }
100    /** @hide */
101    public boolean isFocusPolicy() { return mIsFocusPolicy; }
102
103    /**
104     * The parameter is guaranteed non-null through the Builder
105     */
106    private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper,
107            AudioPolicyFocusListener fl, AudioPolicyStatusListener sl, boolean isFocusPolicy) {
108        mConfig = config;
109        mStatus = POLICY_STATUS_UNREGISTERED;
110        mContext = context;
111        if (looper == null) {
112            looper = Looper.getMainLooper();
113        }
114        if (looper != null) {
115            mEventHandler = new EventHandler(this, looper);
116        } else {
117            mEventHandler = null;
118            Log.e(TAG, "No event handler due to looper without a thread");
119        }
120        mFocusListener = fl;
121        mStatusListener = sl;
122        mIsFocusPolicy = isFocusPolicy;
123    }
124
125    /**
126     * Builder class for {@link AudioPolicy} objects.
127     * By default the policy to be created doesn't govern audio focus decisions.
128     */
129    @SystemApi
130    public static class Builder {
131        private ArrayList<AudioMix> mMixes;
132        private Context mContext;
133        private Looper mLooper;
134        private AudioPolicyFocusListener mFocusListener;
135        private AudioPolicyStatusListener mStatusListener;
136        private boolean mIsFocusPolicy = false;
137
138        /**
139         * Constructs a new Builder with no audio mixes.
140         * @param context the context for the policy
141         */
142        @SystemApi
143        public Builder(Context context) {
144            mMixes = new ArrayList<AudioMix>();
145            mContext = context;
146        }
147
148        /**
149         * Add an {@link AudioMix} to be part of the audio policy being built.
150         * @param mix a non-null {@link AudioMix} to be part of the audio policy.
151         * @return the same Builder instance.
152         * @throws IllegalArgumentException
153         */
154        @SystemApi
155        public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
156            if (mix == null) {
157                throw new IllegalArgumentException("Illegal null AudioMix argument");
158            }
159            mMixes.add(mix);
160            return this;
161        }
162
163        /**
164         * Sets the {@link Looper} on which to run the event loop.
165         * @param looper a non-null specific Looper.
166         * @return the same Builder instance.
167         * @throws IllegalArgumentException
168         */
169        @SystemApi
170        public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
171            if (looper == null) {
172                throw new IllegalArgumentException("Illegal null Looper argument");
173            }
174            mLooper = looper;
175            return this;
176        }
177
178        /**
179         * Sets the audio focus listener for the policy.
180         * @param l a {@link AudioPolicy.AudioPolicyFocusListener}
181         */
182        @SystemApi
183        public void setAudioPolicyFocusListener(AudioPolicyFocusListener l) {
184            mFocusListener = l;
185        }
186
187        /**
188         * Declares whether this policy will grant and deny audio focus through
189         * the {@link AudioPolicy.AudioPolicyStatusListener}.
190         * If set to {@code true}, it is mandatory to set an
191         * {@link AudioPolicy.AudioPolicyStatusListener} in order to successfully build
192         * an {@code AudioPolicy} instance.
193         * @param enforce true if the policy will govern audio focus decisions.
194         * @return the same Builder instance.
195         */
196        @SystemApi
197        public Builder setIsAudioFocusPolicy(boolean isFocusPolicy) {
198            mIsFocusPolicy = isFocusPolicy;
199            return this;
200        }
201
202        /**
203         * Sets the audio policy status listener.
204         * @param l a {@link AudioPolicy.AudioPolicyStatusListener}
205         */
206        @SystemApi
207        public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
208            mStatusListener = l;
209        }
210
211        /**
212         * Combines all of the attributes that have been set on this {@code Builder} and returns a
213         * new {@link AudioPolicy} object.
214         * @return a new {@code AudioPolicy} object.
215         * @throws IllegalStateException if there is no
216         *     {@link AudioPolicy.AudioPolicyStatusListener} but the policy was configured
217         *     as an audio focus policy with {@link #setIsAudioFocusPolicy(boolean)}.
218         */
219        @SystemApi
220        public AudioPolicy build() {
221            if (mStatusListener != null) {
222                // the AudioPolicy status listener includes updates on each mix activity state
223                for (AudioMix mix : mMixes) {
224                    mix.mCallbackFlags |= AudioMix.CALLBACK_FLAG_NOTIFY_ACTIVITY;
225                }
226            }
227            if (mIsFocusPolicy && mFocusListener == null) {
228                throw new IllegalStateException("Cannot be a focus policy without "
229                        + "an AudioPolicyFocusListener");
230            }
231            return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper,
232                    mFocusListener, mStatusListener, mIsFocusPolicy);
233        }
234    }
235
236    public void setRegistration(String regId) {
237        synchronized (mLock) {
238            mRegistrationId = regId;
239            mConfig.setRegistration(regId);
240            if (regId != null) {
241                mStatus = POLICY_STATUS_REGISTERED;
242            } else {
243                mStatus = POLICY_STATUS_UNREGISTERED;
244            }
245        }
246        sendMsg(MSG_POLICY_STATUS_CHANGE);
247    }
248
249    private boolean policyReadyToUse() {
250        synchronized (mLock) {
251            if (mStatus != POLICY_STATUS_REGISTERED) {
252                Log.e(TAG, "Cannot use unregistered AudioPolicy");
253                return false;
254            }
255            if (mContext == null) {
256                Log.e(TAG, "Cannot use AudioPolicy without context");
257                return false;
258            }
259            if (mRegistrationId == null) {
260                Log.e(TAG, "Cannot use unregistered AudioPolicy");
261                return false;
262            }
263        }
264        if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
265                        android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
266            Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid "
267                    + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING");
268            return false;
269        }
270        return true;
271    }
272
273    private void checkMixReadyToUse(AudioMix mix, boolean forTrack)
274            throws IllegalArgumentException{
275        if (mix == null) {
276            String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation"
277                    : "Invalid null AudioMix for AudioRecord creation";
278            throw new IllegalArgumentException(msg);
279        }
280        if (!mConfig.mMixes.contains(mix)) {
281            throw new IllegalArgumentException("Invalid mix: not part of this policy");
282        }
283        if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK)
284        {
285            throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back");
286        }
287        if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) {
288            throw new IllegalArgumentException(
289                    "Invalid AudioMix: not defined for being a recording source");
290        }
291        if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) {
292            throw new IllegalArgumentException(
293                    "Invalid AudioMix: not defined for capturing playback");
294        }
295    }
296
297    /**
298     * Returns the current behavior for audio focus-related ducking.
299     * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
300     */
301    @SystemApi
302    public int getFocusDuckingBehavior() {
303        return mConfig.mDuckingPolicy;
304    }
305
306    // Note on implementation: not part of the Builder as there can be only one registered policy
307    // that handles ducking but there can be multiple policies
308    /**
309     * Sets the behavior for audio focus-related ducking.
310     * There must be a focus listener if this policy is to handle ducking.
311     * @param behavior {@link #FOCUS_POLICY_DUCKING_IN_APP} or
312     *     {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
313     * @return {@link AudioManager#SUCCESS} or {@link AudioManager#ERROR} (for instance if there
314     *     is already an audio policy that handles ducking).
315     * @throws IllegalArgumentException
316     * @throws IllegalStateException
317     */
318    @SystemApi
319    public int setFocusDuckingBehavior(int behavior)
320            throws IllegalArgumentException, IllegalStateException {
321        if ((behavior != FOCUS_POLICY_DUCKING_IN_APP)
322                && (behavior != FOCUS_POLICY_DUCKING_IN_POLICY)) {
323            throw new IllegalArgumentException("Invalid ducking behavior " + behavior);
324        }
325        synchronized (mLock) {
326            if (mStatus != POLICY_STATUS_REGISTERED) {
327                throw new IllegalStateException(
328                        "Cannot change ducking behavior for unregistered policy");
329            }
330            if ((behavior == FOCUS_POLICY_DUCKING_IN_POLICY)
331                    && (mFocusListener == null)) {
332                // there must be a focus listener if the policy handles ducking
333                throw new IllegalStateException(
334                        "Cannot handle ducking without an audio focus listener");
335            }
336            IAudioService service = getService();
337            try {
338                final int status = service.setFocusPropertiesForPolicy(behavior /*duckingBehavior*/,
339                        this.cb());
340                if (status == AudioManager.SUCCESS) {
341                    mConfig.mDuckingPolicy = behavior;
342                }
343                return status;
344            } catch (RemoteException e) {
345                Log.e(TAG, "Dead object in setFocusPropertiesForPolicy for behavior", e);
346                return AudioManager.ERROR;
347            }
348        }
349    }
350
351    /**
352     * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
353     * Audio buffers recorded through the created instance will contain the mix of the audio
354     * streams that fed the given mixer.
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 AudioRecord} 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 AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException {
364        if (!policyReadyToUse()) {
365            Log.e(TAG, "Cannot create AudioRecord sink for AudioMix");
366            return null;
367        }
368        checkMixReadyToUse(mix, false/*not for an AudioTrack*/);
369        // create an AudioFormat from the mix format compatible with recording, as the mix
370        // was defined for playback
371        AudioFormat mixFormat = new AudioFormat.Builder(mix.getFormat())
372                .setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask(
373                        mix.getFormat().getChannelMask()))
374                .build();
375        // create the AudioRecord, configured for loop back, using the same format as the mix
376        AudioRecord ar = new AudioRecord(
377                new AudioAttributes.Builder()
378                        .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
379                        .addTag(addressForTag(mix))
380                        .build(),
381                mixFormat,
382                AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
383                        // using stereo for buffer size to avoid the current poor support for masks
384                        AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()),
385                AudioManager.AUDIO_SESSION_ID_GENERATE
386                );
387        return ar;
388    }
389
390    /**
391     * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}.
392     * Audio buffers played through the created instance will be sent to the given mix
393     * to be recorded through the recording APIs.
394     * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
395     *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
396     * @return a new {@link AudioTrack} instance whose data format is the one defined in the
397     *     {@link AudioMix}, or null if this policy was not successfully registered
398     *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
399     * @throws IllegalArgumentException
400     */
401    @SystemApi
402    public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException {
403        if (!policyReadyToUse()) {
404            Log.e(TAG, "Cannot create AudioTrack source for AudioMix");
405            return null;
406        }
407        checkMixReadyToUse(mix, true/*for an AudioTrack*/);
408        // create the AudioTrack, configured for loop back, using the same format as the mix
409        AudioTrack at = new AudioTrack(
410                new AudioAttributes.Builder()
411                        .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
412                        .addTag(addressForTag(mix))
413                        .build(),
414                mix.getFormat(),
415                AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
416                        mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()),
417                AudioTrack.MODE_STREAM,
418                AudioManager.AUDIO_SESSION_ID_GENERATE
419                );
420        return at;
421    }
422
423    @SystemApi
424    public int getStatus() {
425        return mStatus;
426    }
427
428    @SystemApi
429    public static abstract class AudioPolicyStatusListener {
430        public void onStatusChange() {}
431        public void onMixStateUpdate(AudioMix mix) {}
432    }
433
434    @SystemApi
435    public static abstract class AudioPolicyFocusListener {
436        public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {}
437        public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {}
438        /**
439         * Called whenever an application requests audio focus.
440         * Only ever called if the {@link AudioPolicy} was built with
441         * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}.
442         * @param afi information about the focus request and the requester
443         * @param requestResult the result that was returned synchronously by the framework to the
444         *     application, {@link #AUDIOFOCUS_REQUEST_FAILED},or
445         *     {@link #AUDIOFOCUS_REQUEST_DELAYED}.
446         */
447        public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {}
448        /**
449         * Called whenever an application abandons audio focus.
450         * Only ever called if the {@link AudioPolicy} was built with
451         * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}.
452         * @param afi information about the focus request being abandoned and the original
453         *     requester.
454         */
455        public void onAudioFocusAbandon(AudioFocusInfo afi) {}
456    }
457
458    private void onPolicyStatusChange() {
459        AudioPolicyStatusListener l;
460        synchronized (mLock) {
461            if (mStatusListener == null) {
462                return;
463            }
464            l = mStatusListener;
465        }
466        l.onStatusChange();
467    }
468
469    //==================================================
470    // Callback interface
471
472    /** @hide */
473    public IAudioPolicyCallback cb() { return mPolicyCb; }
474
475    private final IAudioPolicyCallback mPolicyCb = new IAudioPolicyCallback.Stub() {
476
477        public void notifyAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
478            sendMsg(MSG_FOCUS_GRANT, afi, requestResult);
479            if (DEBUG) {
480                Log.v(TAG, "notifyAudioFocusGrant: pack=" + afi.getPackageName() + " client="
481                        + afi.getClientId() + "reqRes=" + requestResult);
482            }
483        }
484
485        public void notifyAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
486            sendMsg(MSG_FOCUS_LOSS, afi, wasNotified ? 1 : 0);
487            if (DEBUG) {
488                Log.v(TAG, "notifyAudioFocusLoss: pack=" + afi.getPackageName() + " client="
489                        + afi.getClientId() + "wasNotified=" + wasNotified);
490            }
491        }
492
493        public void notifyAudioFocusRequest(AudioFocusInfo afi, int requestResult) {
494            sendMsg(MSG_FOCUS_REQUEST, afi, requestResult);
495            if (DEBUG) {
496                Log.v(TAG, "notifyAudioFocusRequest: pack=" + afi.getPackageName() + " client="
497                        + afi.getClientId() + "reqRes=" + requestResult);
498            }
499        }
500
501        public void notifyAudioFocusAbandon(AudioFocusInfo afi) {
502            sendMsg(MSG_FOCUS_ABANDON, afi, 0 /* ignored */);
503            if (DEBUG) {
504                Log.v(TAG, "notifyAudioFocusAbandon: pack=" + afi.getPackageName() + " client="
505                        + afi.getClientId());
506            }
507        }
508
509        public void notifyMixStateUpdate(String regId, int state) {
510            for (AudioMix mix : mConfig.getMixes()) {
511                if (mix.getRegistration().equals(regId)) {
512                    mix.mMixState = state;
513                    sendMsg(MSG_MIX_STATE_UPDATE, mix, 0/*ignored*/);
514                    if (DEBUG) {
515                        Log.v(TAG, "notifyMixStateUpdate: regId=" + regId + " state=" + state);
516                    }
517                }
518            }
519        }
520    };
521
522    //==================================================
523    // Event handling
524    private final EventHandler mEventHandler;
525    private final static int MSG_POLICY_STATUS_CHANGE = 0;
526    private final static int MSG_FOCUS_GRANT = 1;
527    private final static int MSG_FOCUS_LOSS = 2;
528    private final static int MSG_MIX_STATE_UPDATE = 3;
529    private final static int MSG_FOCUS_REQUEST = 4;
530    private final static int MSG_FOCUS_ABANDON = 5;
531
532    private class EventHandler extends Handler {
533        public EventHandler(AudioPolicy ap, Looper looper) {
534            super(looper);
535        }
536
537        @Override
538        public void handleMessage(Message msg) {
539            switch(msg.what) {
540                case MSG_POLICY_STATUS_CHANGE:
541                    onPolicyStatusChange();
542                    break;
543                case MSG_FOCUS_GRANT:
544                    if (mFocusListener != null) {
545                        mFocusListener.onAudioFocusGrant(
546                                (AudioFocusInfo) msg.obj, msg.arg1);
547                    }
548                    break;
549                case MSG_FOCUS_LOSS:
550                    if (mFocusListener != null) {
551                        mFocusListener.onAudioFocusLoss(
552                                (AudioFocusInfo) msg.obj, msg.arg1 != 0);
553                    }
554                    break;
555                case MSG_MIX_STATE_UPDATE:
556                    if (mStatusListener != null) {
557                        mStatusListener.onMixStateUpdate((AudioMix) msg.obj);
558                    }
559                    break;
560                case MSG_FOCUS_REQUEST:
561                    if (mFocusListener != null) {
562                        mFocusListener.onAudioFocusRequest((AudioFocusInfo) msg.obj, msg.arg1);
563                    } else { // should never be null, but don't crash
564                        Log.e(TAG, "Invalid null focus listener for focus request event");
565                    }
566                    break;
567                case MSG_FOCUS_ABANDON:
568                    if (mFocusListener != null) { // should never be null
569                        mFocusListener.onAudioFocusAbandon((AudioFocusInfo) msg.obj);
570                    } else { // should never be null, but don't crash
571                        Log.e(TAG, "Invalid null focus listener for focus abandon event");
572                    }
573                    break;
574                default:
575                    Log.e(TAG, "Unknown event " + msg.what);
576            }
577        }
578    }
579
580    //==========================================================
581    // Utils
582    private static String addressForTag(AudioMix mix) {
583        return "addr=" + mix.getRegistration();
584    }
585
586    private void sendMsg(int msg) {
587        if (mEventHandler != null) {
588            mEventHandler.sendEmptyMessage(msg);
589        }
590    }
591
592    private void sendMsg(int msg, Object obj, int i) {
593        if (mEventHandler != null) {
594            mEventHandler.sendMessage(
595                    mEventHandler.obtainMessage(msg, i /*arg1*/, 0 /*arg2, ignored*/, obj));
596        }
597    }
598
599    private static IAudioService sService;
600
601    private static IAudioService getService()
602    {
603        if (sService != null) {
604            return sService;
605        }
606        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
607        sService = IAudioService.Stub.asInterface(b);
608        return sService;
609    }
610
611    public String toLogFriendlyString() {
612        String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
613        textDump += "config=" + mConfig.toLogFriendlyString();
614        return (textDump);
615    }
616
617    /** @hide */
618    @IntDef({
619        POLICY_STATUS_REGISTERED,
620        POLICY_STATUS_UNREGISTERED
621    })
622    @Retention(RetentionPolicy.SOURCE)
623    public @interface PolicyStatus {}
624}
625