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            if (mStatusListener != null) {
193                // the AudioPolicy status listener includes updates on each mix activity state
194                for (AudioMix mix : mMixes) {
195                    mix.mCallbackFlags |= AudioMix.CALLBACK_FLAG_NOTIFY_ACTIVITY;
196                }
197            }
198            return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper,
199                    mFocusListener, mStatusListener);
200        }
201    }
202
203    public void setRegistration(String regId) {
204        synchronized (mLock) {
205            mRegistrationId = regId;
206            mConfig.setRegistration(regId);
207            if (regId != null) {
208                mStatus = POLICY_STATUS_REGISTERED;
209            } else {
210                mStatus = POLICY_STATUS_UNREGISTERED;
211            }
212        }
213        sendMsg(MSG_POLICY_STATUS_CHANGE);
214    }
215
216    private boolean policyReadyToUse() {
217        synchronized (mLock) {
218            if (mStatus != POLICY_STATUS_REGISTERED) {
219                Log.e(TAG, "Cannot use unregistered AudioPolicy");
220                return false;
221            }
222            if (mContext == null) {
223                Log.e(TAG, "Cannot use AudioPolicy without context");
224                return false;
225            }
226            if (mRegistrationId == null) {
227                Log.e(TAG, "Cannot use unregistered AudioPolicy");
228                return false;
229            }
230        }
231        if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
232                        android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
233            Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid "
234                    + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING");
235            return false;
236        }
237        return true;
238    }
239
240    private void checkMixReadyToUse(AudioMix mix, boolean forTrack)
241            throws IllegalArgumentException{
242        if (mix == null) {
243            String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation"
244                    : "Invalid null AudioMix for AudioRecord creation";
245            throw new IllegalArgumentException(msg);
246        }
247        if (!mConfig.mMixes.contains(mix)) {
248            throw new IllegalArgumentException("Invalid mix: not part of this policy");
249        }
250        if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK)
251        {
252            throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back");
253        }
254        if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) {
255            throw new IllegalArgumentException(
256                    "Invalid AudioMix: not defined for being a recording source");
257        }
258        if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) {
259            throw new IllegalArgumentException(
260                    "Invalid AudioMix: not defined for capturing playback");
261        }
262    }
263
264    /**
265     * Returns the current behavior for audio focus-related ducking.
266     * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
267     */
268    @SystemApi
269    public int getFocusDuckingBehavior() {
270        return mConfig.mDuckingPolicy;
271    }
272
273    // Note on implementation: not part of the Builder as there can be only one registered policy
274    // that handles ducking but there can be multiple policies
275    /**
276     * Sets the behavior for audio focus-related ducking.
277     * There must be a focus listener if this policy is to handle ducking.
278     * @param behavior {@link #FOCUS_POLICY_DUCKING_IN_APP} or
279     *     {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
280     * @return {@link AudioManager#SUCCESS} or {@link AudioManager#ERROR} (for instance if there
281     *     is already an audio policy that handles ducking).
282     * @throws IllegalArgumentException
283     * @throws IllegalStateException
284     */
285    @SystemApi
286    public int setFocusDuckingBehavior(int behavior)
287            throws IllegalArgumentException, IllegalStateException {
288        if ((behavior != FOCUS_POLICY_DUCKING_IN_APP)
289                && (behavior != FOCUS_POLICY_DUCKING_IN_POLICY)) {
290            throw new IllegalArgumentException("Invalid ducking behavior " + behavior);
291        }
292        synchronized (mLock) {
293            if (mStatus != POLICY_STATUS_REGISTERED) {
294                throw new IllegalStateException(
295                        "Cannot change ducking behavior for unregistered policy");
296            }
297            if ((behavior == FOCUS_POLICY_DUCKING_IN_POLICY)
298                    && (mFocusListener == null)) {
299                // there must be a focus listener if the policy handles ducking
300                throw new IllegalStateException(
301                        "Cannot handle ducking without an audio focus listener");
302            }
303            IAudioService service = getService();
304            try {
305                final int status = service.setFocusPropertiesForPolicy(behavior /*duckingBehavior*/,
306                        this.cb());
307                if (status == AudioManager.SUCCESS) {
308                    mConfig.mDuckingPolicy = behavior;
309                }
310                return status;
311            } catch (RemoteException e) {
312                Log.e(TAG, "Dead object in setFocusPropertiesForPolicy for behavior", e);
313                return AudioManager.ERROR;
314            }
315        }
316    }
317
318    /**
319     * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
320     * Audio buffers recorded through the created instance will contain the mix of the audio
321     * streams that fed the given mixer.
322     * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
323     *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
324     * @return a new {@link AudioRecord} instance whose data format is the one defined in the
325     *     {@link AudioMix}, or null if this policy was not successfully registered
326     *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
327     * @throws IllegalArgumentException
328     */
329    @SystemApi
330    public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException {
331        if (!policyReadyToUse()) {
332            Log.e(TAG, "Cannot create AudioRecord sink for AudioMix");
333            return null;
334        }
335        checkMixReadyToUse(mix, false/*not for an AudioTrack*/);
336        // create an AudioFormat from the mix format compatible with recording, as the mix
337        // was defined for playback
338        AudioFormat mixFormat = new AudioFormat.Builder(mix.getFormat())
339                .setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask(
340                        mix.getFormat().getChannelMask()))
341                .build();
342        // create the AudioRecord, configured for loop back, using the same format as the mix
343        AudioRecord ar = new AudioRecord(
344                new AudioAttributes.Builder()
345                        .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
346                        .addTag(addressForTag(mix))
347                        .build(),
348                mixFormat,
349                AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
350                        // using stereo for buffer size to avoid the current poor support for masks
351                        AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()),
352                AudioManager.AUDIO_SESSION_ID_GENERATE
353                );
354        return ar;
355    }
356
357    /**
358     * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}.
359     * Audio buffers played through the created instance will be sent to the given mix
360     * to be recorded through the recording APIs.
361     * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
362     *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
363     * @return a new {@link AudioTrack} instance whose data format is the one defined in the
364     *     {@link AudioMix}, or null if this policy was not successfully registered
365     *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
366     * @throws IllegalArgumentException
367     */
368    @SystemApi
369    public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException {
370        if (!policyReadyToUse()) {
371            Log.e(TAG, "Cannot create AudioTrack source for AudioMix");
372            return null;
373        }
374        checkMixReadyToUse(mix, true/*for an AudioTrack*/);
375        // create the AudioTrack, configured for loop back, using the same format as the mix
376        AudioTrack at = new AudioTrack(
377                new AudioAttributes.Builder()
378                        .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
379                        .addTag(addressForTag(mix))
380                        .build(),
381                mix.getFormat(),
382                AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
383                        mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()),
384                AudioTrack.MODE_STREAM,
385                AudioManager.AUDIO_SESSION_ID_GENERATE
386                );
387        return at;
388    }
389
390    @SystemApi
391    public int getStatus() {
392        return mStatus;
393    }
394
395    @SystemApi
396    public static abstract class AudioPolicyStatusListener {
397        public void onStatusChange() {}
398        public void onMixStateUpdate(AudioMix mix) {}
399    }
400
401    @SystemApi
402    public static abstract class AudioPolicyFocusListener {
403        public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {}
404        public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {}
405    }
406
407    private void onPolicyStatusChange() {
408        AudioPolicyStatusListener l;
409        synchronized (mLock) {
410            if (mStatusListener == null) {
411                return;
412            }
413            l = mStatusListener;
414        }
415        l.onStatusChange();
416    }
417
418    //==================================================
419    // Callback interface
420
421    /** @hide */
422    public IAudioPolicyCallback cb() { return mPolicyCb; }
423
424    private final IAudioPolicyCallback mPolicyCb = new IAudioPolicyCallback.Stub() {
425
426        public void notifyAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
427            sendMsg(MSG_FOCUS_GRANT, afi, requestResult);
428            if (DEBUG) {
429                Log.v(TAG, "notifyAudioFocusGrant: pack=" + afi.getPackageName() + " client="
430                        + afi.getClientId() + "reqRes=" + requestResult);
431            }
432        }
433
434        public void notifyAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
435            sendMsg(MSG_FOCUS_LOSS, afi, wasNotified ? 1 : 0);
436            if (DEBUG) {
437                Log.v(TAG, "notifyAudioFocusLoss: pack=" + afi.getPackageName() + " client="
438                        + afi.getClientId() + "wasNotified=" + wasNotified);
439            }
440        }
441
442        public void notifyMixStateUpdate(String regId, int state) {
443            for (AudioMix mix : mConfig.getMixes()) {
444                if (mix.getRegistration().equals(regId)) {
445                    mix.mMixState = state;
446                    sendMsg(MSG_MIX_STATE_UPDATE, mix, 0/*ignored*/);
447                    if (DEBUG) {
448                        Log.v(TAG, "notifyMixStateUpdate: regId=" + regId + " state=" + state);
449                    }
450                }
451            }
452        }
453    };
454
455    //==================================================
456    // Event handling
457    private final EventHandler mEventHandler;
458    private final static int MSG_POLICY_STATUS_CHANGE = 0;
459    private final static int MSG_FOCUS_GRANT = 1;
460    private final static int MSG_FOCUS_LOSS = 2;
461    private final static int MSG_MIX_STATE_UPDATE = 3;
462
463    private class EventHandler extends Handler {
464        public EventHandler(AudioPolicy ap, Looper looper) {
465            super(looper);
466        }
467
468        @Override
469        public void handleMessage(Message msg) {
470            switch(msg.what) {
471                case MSG_POLICY_STATUS_CHANGE:
472                    onPolicyStatusChange();
473                    break;
474                case MSG_FOCUS_GRANT:
475                    if (mFocusListener != null) {
476                        mFocusListener.onAudioFocusGrant(
477                                (AudioFocusInfo) msg.obj, msg.arg1);
478                    }
479                    break;
480                case MSG_FOCUS_LOSS:
481                    if (mFocusListener != null) {
482                        mFocusListener.onAudioFocusLoss(
483                                (AudioFocusInfo) msg.obj, msg.arg1 != 0);
484                    }
485                    break;
486                case MSG_MIX_STATE_UPDATE:
487                    if (mStatusListener != null) {
488                        mStatusListener.onMixStateUpdate((AudioMix) msg.obj);
489                    }
490                    break;
491                default:
492                    Log.e(TAG, "Unknown event " + msg.what);
493            }
494        }
495    }
496
497    //==========================================================
498    // Utils
499    private static String addressForTag(AudioMix mix) {
500        return "addr=" + mix.getRegistration();
501    }
502
503    private void sendMsg(int msg) {
504        if (mEventHandler != null) {
505            mEventHandler.sendEmptyMessage(msg);
506        }
507    }
508
509    private void sendMsg(int msg, Object obj, int i) {
510        if (mEventHandler != null) {
511            mEventHandler.sendMessage(
512                    mEventHandler.obtainMessage(msg, i /*arg1*/, 0 /*arg2, ignored*/, obj));
513        }
514    }
515
516    private static IAudioService sService;
517
518    private static IAudioService getService()
519    {
520        if (sService != null) {
521            return sService;
522        }
523        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
524        sService = IAudioService.Stub.asInterface(b);
525        return sService;
526    }
527
528    public String toLogFriendlyString() {
529        String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
530        textDump += "config=" + mConfig.toLogFriendlyString();
531        return (textDump);
532    }
533
534    /** @hide */
535    @IntDef({
536        POLICY_STATUS_REGISTERED,
537        POLICY_STATUS_UNREGISTERED
538    })
539    @Retention(RetentionPolicy.SOURCE)
540    public @interface PolicyStatus {}
541}
542