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;
45import java.util.List;
46
47/**
48 * @hide
49 * AudioPolicy provides access to the management of audio routing and audio focus.
50 */
51@SystemApi
52public class AudioPolicy {
53
54    private static final String TAG = "AudioPolicy";
55    private static final boolean DEBUG = false;
56    private final Object mLock = new Object();
57
58    /**
59     * The status of an audio policy that is valid but cannot be used because it is not registered.
60     */
61    @SystemApi
62    public static final int POLICY_STATUS_UNREGISTERED = 1;
63    /**
64     * The status of an audio policy that is valid, successfully registered and thus active.
65     */
66    @SystemApi
67    public static final int POLICY_STATUS_REGISTERED = 2;
68
69    private int mStatus;
70    private String mRegistrationId;
71    private AudioPolicyStatusListener mStatusListener;
72    private boolean mIsFocusPolicy;
73
74    /**
75     * The behavior of a policy with regards to audio focus where it relies on the application
76     * to do the ducking, the is the legacy and default behavior.
77     */
78    @SystemApi
79    public static final int FOCUS_POLICY_DUCKING_IN_APP = 0;
80    public static final int FOCUS_POLICY_DUCKING_DEFAULT = FOCUS_POLICY_DUCKING_IN_APP;
81    /**
82     * The behavior of a policy with regards to audio focus where it handles ducking instead
83     * of the application losing focus and being signaled it can duck (as communicated by
84     * {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}).
85     * <br>Can only be used after having set a listener with
86     * {@link AudioPolicy#setAudioPolicyFocusListener(AudioPolicyFocusListener)}.
87     */
88    @SystemApi
89    public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1;
90
91    private AudioPolicyFocusListener mFocusListener;
92
93    private final AudioPolicyVolumeCallback mVolCb;
94
95    private Context mContext;
96
97    private AudioPolicyConfig mConfig;
98
99    /** @hide */
100    public AudioPolicyConfig getConfig() { return mConfig; }
101    /** @hide */
102    public boolean hasFocusListener() { return mFocusListener != null; }
103    /** @hide */
104    public boolean isFocusPolicy() { return mIsFocusPolicy; }
105    /** @hide */
106    public boolean isVolumeController() { return mVolCb != null; }
107
108    /**
109     * The parameter is guaranteed non-null through the Builder
110     */
111    private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper,
112            AudioPolicyFocusListener fl, AudioPolicyStatusListener sl, boolean isFocusPolicy,
113            AudioPolicyVolumeCallback vc) {
114        mConfig = config;
115        mStatus = POLICY_STATUS_UNREGISTERED;
116        mContext = context;
117        if (looper == null) {
118            looper = Looper.getMainLooper();
119        }
120        if (looper != null) {
121            mEventHandler = new EventHandler(this, looper);
122        } else {
123            mEventHandler = null;
124            Log.e(TAG, "No event handler due to looper without a thread");
125        }
126        mFocusListener = fl;
127        mStatusListener = sl;
128        mIsFocusPolicy = isFocusPolicy;
129        mVolCb = vc;
130    }
131
132    /**
133     * Builder class for {@link AudioPolicy} objects.
134     * By default the policy to be created doesn't govern audio focus decisions.
135     */
136    @SystemApi
137    public static class Builder {
138        private ArrayList<AudioMix> mMixes;
139        private Context mContext;
140        private Looper mLooper;
141        private AudioPolicyFocusListener mFocusListener;
142        private AudioPolicyStatusListener mStatusListener;
143        private boolean mIsFocusPolicy = false;
144        private AudioPolicyVolumeCallback mVolCb;
145
146        /**
147         * Constructs a new Builder with no audio mixes.
148         * @param context the context for the policy
149         */
150        @SystemApi
151        public Builder(Context context) {
152            mMixes = new ArrayList<AudioMix>();
153            mContext = context;
154        }
155
156        /**
157         * Add an {@link AudioMix} to be part of the audio policy being built.
158         * @param mix a non-null {@link AudioMix} to be part of the audio policy.
159         * @return the same Builder instance.
160         * @throws IllegalArgumentException
161         */
162        @SystemApi
163        public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
164            if (mix == null) {
165                throw new IllegalArgumentException("Illegal null AudioMix argument");
166            }
167            mMixes.add(mix);
168            return this;
169        }
170
171        /**
172         * Sets the {@link Looper} on which to run the event loop.
173         * @param looper a non-null specific Looper.
174         * @return the same Builder instance.
175         * @throws IllegalArgumentException
176         */
177        @SystemApi
178        public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
179            if (looper == null) {
180                throw new IllegalArgumentException("Illegal null Looper argument");
181            }
182            mLooper = looper;
183            return this;
184        }
185
186        /**
187         * Sets the audio focus listener for the policy.
188         * @param l a {@link AudioPolicy.AudioPolicyFocusListener}
189         */
190        @SystemApi
191        public void setAudioPolicyFocusListener(AudioPolicyFocusListener l) {
192            mFocusListener = l;
193        }
194
195        /**
196         * Declares whether this policy will grant and deny audio focus through
197         * the {@link AudioPolicy.AudioPolicyFocusListener}.
198         * If set to {@code true}, it is mandatory to set an
199         * {@link AudioPolicy.AudioPolicyFocusListener} in order to successfully build
200         * an {@code AudioPolicy} instance.
201         * @param enforce true if the policy will govern audio focus decisions.
202         * @return the same Builder instance.
203         */
204        @SystemApi
205        public Builder setIsAudioFocusPolicy(boolean isFocusPolicy) {
206            mIsFocusPolicy = isFocusPolicy;
207            return this;
208        }
209
210        /**
211         * Sets the audio policy status listener.
212         * @param l a {@link AudioPolicy.AudioPolicyStatusListener}
213         */
214        @SystemApi
215        public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
216            mStatusListener = l;
217        }
218
219        @SystemApi
220        /**
221         * Sets the callback to receive all volume key-related events.
222         * The callback will only be called if the device is configured to handle volume events
223         * in the PhoneWindowManager (see config_handleVolumeKeysInWindowManager)
224         * @param vc
225         * @return the same Builder instance.
226         */
227        public Builder setAudioPolicyVolumeCallback(@NonNull AudioPolicyVolumeCallback vc) {
228            if (vc == null) {
229                throw new IllegalArgumentException("Invalid null volume callback");
230            }
231            mVolCb = vc;
232            return this;
233        }
234
235        /**
236         * Combines all of the attributes that have been set on this {@code Builder} and returns a
237         * new {@link AudioPolicy} object.
238         * @return a new {@code AudioPolicy} object.
239         * @throws IllegalStateException if there is no
240         *     {@link AudioPolicy.AudioPolicyStatusListener} but the policy was configured
241         *     as an audio focus policy with {@link #setIsAudioFocusPolicy(boolean)}.
242         */
243        @SystemApi
244        public AudioPolicy build() {
245            if (mStatusListener != null) {
246                // the AudioPolicy status listener includes updates on each mix activity state
247                for (AudioMix mix : mMixes) {
248                    mix.mCallbackFlags |= AudioMix.CALLBACK_FLAG_NOTIFY_ACTIVITY;
249                }
250            }
251            if (mIsFocusPolicy && mFocusListener == null) {
252                throw new IllegalStateException("Cannot be a focus policy without "
253                        + "an AudioPolicyFocusListener");
254            }
255            return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper,
256                    mFocusListener, mStatusListener, mIsFocusPolicy, mVolCb);
257        }
258    }
259
260    /**
261     * @hide
262     * Update the current configuration of the set of audio mixes by adding new ones, while
263     * keeping the policy registered.
264     * This method can only be called on a registered policy.
265     * @param mixes the list of {@link AudioMix} to add
266     * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR}
267     *    otherwise.
268     */
269    @SystemApi
270    public int attachMixes(@NonNull List<AudioMix> mixes) {
271        if (mixes == null) {
272            throw new IllegalArgumentException("Illegal null list of AudioMix");
273        }
274        synchronized (mLock) {
275            if (mStatus != POLICY_STATUS_REGISTERED) {
276                throw new IllegalStateException("Cannot alter unregistered AudioPolicy");
277            }
278            final ArrayList<AudioMix> zeMixes = new ArrayList<AudioMix>(mixes.size());
279            for (AudioMix mix : mixes) {
280                if (mix == null) {
281                    throw new IllegalArgumentException("Illegal null AudioMix in attachMixes");
282                } else {
283                    zeMixes.add(mix);
284                }
285            }
286            final AudioPolicyConfig cfg = new AudioPolicyConfig(zeMixes);
287            IAudioService service = getService();
288            try {
289                final int status = service.addMixForPolicy(cfg, this.cb());
290                if (status == AudioManager.SUCCESS) {
291                    mConfig.add(zeMixes);
292                }
293                return status;
294            } catch (RemoteException e) {
295                Log.e(TAG, "Dead object in attachMixes", e);
296                return AudioManager.ERROR;
297            }
298        }
299    }
300
301    /**
302     * @hide
303     * Update the current configuration of the set of audio mixes by removing some, while
304     * keeping the policy registered.
305     * This method can only be called on a registered policy.
306     * @param mixes the list of {@link AudioMix} to remove
307     * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR}
308     *    otherwise.
309     */
310    @SystemApi
311    public int detachMixes(@NonNull List<AudioMix> mixes) {
312        if (mixes == null) {
313            throw new IllegalArgumentException("Illegal null list of AudioMix");
314        }
315        synchronized (mLock) {
316            if (mStatus != POLICY_STATUS_REGISTERED) {
317                throw new IllegalStateException("Cannot alter unregistered AudioPolicy");
318            }
319            final ArrayList<AudioMix> zeMixes = new ArrayList<AudioMix>(mixes.size());
320            for (AudioMix mix : mixes) {
321                if (mix == null) {
322                    throw new IllegalArgumentException("Illegal null AudioMix in detachMixes");
323                    // TODO also check mix is currently contained in list of mixes
324                } else {
325                    zeMixes.add(mix);
326                }
327            }
328            final AudioPolicyConfig cfg = new AudioPolicyConfig(zeMixes);
329            IAudioService service = getService();
330            try {
331                final int status = service.removeMixForPolicy(cfg, this.cb());
332                if (status == AudioManager.SUCCESS) {
333                    mConfig.remove(zeMixes);
334                }
335                return status;
336            } catch (RemoteException e) {
337                Log.e(TAG, "Dead object in detachMixes", e);
338                return AudioManager.ERROR;
339            }
340        }
341    }
342
343    public void setRegistration(String regId) {
344        synchronized (mLock) {
345            mRegistrationId = regId;
346            mConfig.setRegistration(regId);
347            if (regId != null) {
348                mStatus = POLICY_STATUS_REGISTERED;
349            } else {
350                mStatus = POLICY_STATUS_UNREGISTERED;
351            }
352        }
353        sendMsg(MSG_POLICY_STATUS_CHANGE);
354    }
355
356    private boolean policyReadyToUse() {
357        synchronized (mLock) {
358            if (mStatus != POLICY_STATUS_REGISTERED) {
359                Log.e(TAG, "Cannot use unregistered AudioPolicy");
360                return false;
361            }
362            if (mContext == null) {
363                Log.e(TAG, "Cannot use AudioPolicy without context");
364                return false;
365            }
366            if (mRegistrationId == null) {
367                Log.e(TAG, "Cannot use unregistered AudioPolicy");
368                return false;
369            }
370        }
371        if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
372                        android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
373            Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid "
374                    + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING");
375            return false;
376        }
377        return true;
378    }
379
380    private void checkMixReadyToUse(AudioMix mix, boolean forTrack)
381            throws IllegalArgumentException{
382        if (mix == null) {
383            String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation"
384                    : "Invalid null AudioMix for AudioRecord creation";
385            throw new IllegalArgumentException(msg);
386        }
387        if (!mConfig.mMixes.contains(mix)) {
388            throw new IllegalArgumentException("Invalid mix: not part of this policy");
389        }
390        if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK)
391        {
392            throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back");
393        }
394        if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) {
395            throw new IllegalArgumentException(
396                    "Invalid AudioMix: not defined for being a recording source");
397        }
398        if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) {
399            throw new IllegalArgumentException(
400                    "Invalid AudioMix: not defined for capturing playback");
401        }
402    }
403
404    /**
405     * Returns the current behavior for audio focus-related ducking.
406     * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
407     */
408    @SystemApi
409    public int getFocusDuckingBehavior() {
410        return mConfig.mDuckingPolicy;
411    }
412
413    // Note on implementation: not part of the Builder as there can be only one registered policy
414    // that handles ducking but there can be multiple policies
415    /**
416     * Sets the behavior for audio focus-related ducking.
417     * There must be a focus listener if this policy is to handle ducking.
418     * @param behavior {@link #FOCUS_POLICY_DUCKING_IN_APP} or
419     *     {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
420     * @return {@link AudioManager#SUCCESS} or {@link AudioManager#ERROR} (for instance if there
421     *     is already an audio policy that handles ducking).
422     * @throws IllegalArgumentException
423     * @throws IllegalStateException
424     */
425    @SystemApi
426    public int setFocusDuckingBehavior(int behavior)
427            throws IllegalArgumentException, IllegalStateException {
428        if ((behavior != FOCUS_POLICY_DUCKING_IN_APP)
429                && (behavior != FOCUS_POLICY_DUCKING_IN_POLICY)) {
430            throw new IllegalArgumentException("Invalid ducking behavior " + behavior);
431        }
432        synchronized (mLock) {
433            if (mStatus != POLICY_STATUS_REGISTERED) {
434                throw new IllegalStateException(
435                        "Cannot change ducking behavior for unregistered policy");
436            }
437            if ((behavior == FOCUS_POLICY_DUCKING_IN_POLICY)
438                    && (mFocusListener == null)) {
439                // there must be a focus listener if the policy handles ducking
440                throw new IllegalStateException(
441                        "Cannot handle ducking without an audio focus listener");
442            }
443            IAudioService service = getService();
444            try {
445                final int status = service.setFocusPropertiesForPolicy(behavior /*duckingBehavior*/,
446                        this.cb());
447                if (status == AudioManager.SUCCESS) {
448                    mConfig.mDuckingPolicy = behavior;
449                }
450                return status;
451            } catch (RemoteException e) {
452                Log.e(TAG, "Dead object in setFocusPropertiesForPolicy for behavior", e);
453                return AudioManager.ERROR;
454            }
455        }
456    }
457
458    /**
459     * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
460     * Audio buffers recorded through the created instance will contain the mix of the audio
461     * streams that fed the given mixer.
462     * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
463     *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
464     * @return a new {@link AudioRecord} instance whose data format is the one defined in the
465     *     {@link AudioMix}, or null if this policy was not successfully registered
466     *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
467     * @throws IllegalArgumentException
468     */
469    @SystemApi
470    public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException {
471        if (!policyReadyToUse()) {
472            Log.e(TAG, "Cannot create AudioRecord sink for AudioMix");
473            return null;
474        }
475        checkMixReadyToUse(mix, false/*not for an AudioTrack*/);
476        // create an AudioFormat from the mix format compatible with recording, as the mix
477        // was defined for playback
478        AudioFormat mixFormat = new AudioFormat.Builder(mix.getFormat())
479                .setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask(
480                        mix.getFormat().getChannelMask()))
481                .build();
482        // create the AudioRecord, configured for loop back, using the same format as the mix
483        AudioRecord ar = new AudioRecord(
484                new AudioAttributes.Builder()
485                        .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
486                        .addTag(addressForTag(mix))
487                        .addTag(AudioRecord.SUBMIX_FIXED_VOLUME)
488                        .build(),
489                mixFormat,
490                AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
491                        // using stereo for buffer size to avoid the current poor support for masks
492                        AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()),
493                AudioManager.AUDIO_SESSION_ID_GENERATE
494                );
495        return ar;
496    }
497
498    /**
499     * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}.
500     * Audio buffers played through the created instance will be sent to the given mix
501     * to be recorded through the recording APIs.
502     * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
503     *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
504     * @return a new {@link AudioTrack} instance whose data format is the one defined in the
505     *     {@link AudioMix}, or null if this policy was not successfully registered
506     *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
507     * @throws IllegalArgumentException
508     */
509    @SystemApi
510    public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException {
511        if (!policyReadyToUse()) {
512            Log.e(TAG, "Cannot create AudioTrack source for AudioMix");
513            return null;
514        }
515        checkMixReadyToUse(mix, true/*for an AudioTrack*/);
516        // create the AudioTrack, configured for loop back, using the same format as the mix
517        AudioTrack at = new AudioTrack(
518                new AudioAttributes.Builder()
519                        .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
520                        .addTag(addressForTag(mix))
521                        .build(),
522                mix.getFormat(),
523                AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
524                        mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()),
525                AudioTrack.MODE_STREAM,
526                AudioManager.AUDIO_SESSION_ID_GENERATE
527                );
528        return at;
529    }
530
531    @SystemApi
532    public int getStatus() {
533        return mStatus;
534    }
535
536    @SystemApi
537    public static abstract class AudioPolicyStatusListener {
538        public void onStatusChange() {}
539        public void onMixStateUpdate(AudioMix mix) {}
540    }
541
542    @SystemApi
543    public static abstract class AudioPolicyFocusListener {
544        public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {}
545        public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {}
546        /**
547         * Called whenever an application requests audio focus.
548         * Only ever called if the {@link AudioPolicy} was built with
549         * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}.
550         * @param afi information about the focus request and the requester
551         * @param requestResult deprecated after the addition of
552         *     {@link AudioManager#setFocusRequestResult(AudioFocusInfo, int, AudioPolicy)}
553         *     in Android P, always equal to {@link #AUDIOFOCUS_REQUEST_GRANTED}.
554         */
555        public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {}
556        /**
557         * Called whenever an application abandons audio focus.
558         * Only ever called if the {@link AudioPolicy} was built with
559         * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}.
560         * @param afi information about the focus request being abandoned and the original
561         *     requester.
562         */
563        public void onAudioFocusAbandon(AudioFocusInfo afi) {}
564    }
565
566    @SystemApi
567    /**
568     * Callback class to receive volume change-related events.
569     * See {@link #Builder.setAudioPolicyVolumeCallback(AudioPolicyCallback)} to configure the
570     * {@link AudioPolicy} to receive those events.
571     *
572     */
573    public static abstract class AudioPolicyVolumeCallback {
574        /** @hide */
575        public AudioPolicyVolumeCallback() {}
576        /**
577         * Called when volume key-related changes are triggered, on the key down event.
578         * @param adjustment the type of volume adjustment for the key.
579         */
580        public void onVolumeAdjustment(@AudioManager.VolumeAdjustment int adjustment) {}
581    }
582
583    private void onPolicyStatusChange() {
584        AudioPolicyStatusListener l;
585        synchronized (mLock) {
586            if (mStatusListener == null) {
587                return;
588            }
589            l = mStatusListener;
590        }
591        l.onStatusChange();
592    }
593
594    //==================================================
595    // Callback interface
596
597    /** @hide */
598    public IAudioPolicyCallback cb() { return mPolicyCb; }
599
600    private final IAudioPolicyCallback mPolicyCb = new IAudioPolicyCallback.Stub() {
601
602        public void notifyAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
603            sendMsg(MSG_FOCUS_GRANT, afi, requestResult);
604            if (DEBUG) {
605                Log.v(TAG, "notifyAudioFocusGrant: pack=" + afi.getPackageName() + " client="
606                        + afi.getClientId() + "reqRes=" + requestResult);
607            }
608        }
609
610        public void notifyAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
611            sendMsg(MSG_FOCUS_LOSS, afi, wasNotified ? 1 : 0);
612            if (DEBUG) {
613                Log.v(TAG, "notifyAudioFocusLoss: pack=" + afi.getPackageName() + " client="
614                        + afi.getClientId() + "wasNotified=" + wasNotified);
615            }
616        }
617
618        public void notifyAudioFocusRequest(AudioFocusInfo afi, int requestResult) {
619            sendMsg(MSG_FOCUS_REQUEST, afi, requestResult);
620            if (DEBUG) {
621                Log.v(TAG, "notifyAudioFocusRequest: pack=" + afi.getPackageName() + " client="
622                        + afi.getClientId() + " gen=" + afi.getGen());
623            }
624        }
625
626        public void notifyAudioFocusAbandon(AudioFocusInfo afi) {
627            sendMsg(MSG_FOCUS_ABANDON, afi, 0 /* ignored */);
628            if (DEBUG) {
629                Log.v(TAG, "notifyAudioFocusAbandon: pack=" + afi.getPackageName() + " client="
630                        + afi.getClientId());
631            }
632        }
633
634        public void notifyMixStateUpdate(String regId, int state) {
635            for (AudioMix mix : mConfig.getMixes()) {
636                if (mix.getRegistration().equals(regId)) {
637                    mix.mMixState = state;
638                    sendMsg(MSG_MIX_STATE_UPDATE, mix, 0/*ignored*/);
639                    if (DEBUG) {
640                        Log.v(TAG, "notifyMixStateUpdate: regId=" + regId + " state=" + state);
641                    }
642                }
643            }
644        }
645
646        public void notifyVolumeAdjust(int adjustment) {
647            sendMsg(MSG_VOL_ADJUST, null /* ignored */, adjustment);
648            if (DEBUG) {
649                Log.v(TAG, "notifyVolumeAdjust: " + adjustment);
650            }
651        }
652    };
653
654    //==================================================
655    // Event handling
656    private final EventHandler mEventHandler;
657    private final static int MSG_POLICY_STATUS_CHANGE = 0;
658    private final static int MSG_FOCUS_GRANT = 1;
659    private final static int MSG_FOCUS_LOSS = 2;
660    private final static int MSG_MIX_STATE_UPDATE = 3;
661    private final static int MSG_FOCUS_REQUEST = 4;
662    private final static int MSG_FOCUS_ABANDON = 5;
663    private final static int MSG_VOL_ADJUST = 6;
664
665    private class EventHandler extends Handler {
666        public EventHandler(AudioPolicy ap, Looper looper) {
667            super(looper);
668        }
669
670        @Override
671        public void handleMessage(Message msg) {
672            switch(msg.what) {
673                case MSG_POLICY_STATUS_CHANGE:
674                    onPolicyStatusChange();
675                    break;
676                case MSG_FOCUS_GRANT:
677                    if (mFocusListener != null) {
678                        mFocusListener.onAudioFocusGrant(
679                                (AudioFocusInfo) msg.obj, msg.arg1);
680                    }
681                    break;
682                case MSG_FOCUS_LOSS:
683                    if (mFocusListener != null) {
684                        mFocusListener.onAudioFocusLoss(
685                                (AudioFocusInfo) msg.obj, msg.arg1 != 0);
686                    }
687                    break;
688                case MSG_MIX_STATE_UPDATE:
689                    if (mStatusListener != null) {
690                        mStatusListener.onMixStateUpdate((AudioMix) msg.obj);
691                    }
692                    break;
693                case MSG_FOCUS_REQUEST:
694                    if (mFocusListener != null) {
695                        mFocusListener.onAudioFocusRequest((AudioFocusInfo) msg.obj, msg.arg1);
696                    } else { // should never be null, but don't crash
697                        Log.e(TAG, "Invalid null focus listener for focus request event");
698                    }
699                    break;
700                case MSG_FOCUS_ABANDON:
701                    if (mFocusListener != null) { // should never be null
702                        mFocusListener.onAudioFocusAbandon((AudioFocusInfo) msg.obj);
703                    } else { // should never be null, but don't crash
704                        Log.e(TAG, "Invalid null focus listener for focus abandon event");
705                    }
706                    break;
707                case MSG_VOL_ADJUST:
708                    if (mVolCb != null) {
709                        mVolCb.onVolumeAdjustment(msg.arg1);
710                    } else { // should never be null, but don't crash
711                        Log.e(TAG, "Invalid null volume event");
712                    }
713                    break;
714                default:
715                    Log.e(TAG, "Unknown event " + msg.what);
716            }
717        }
718    }
719
720    //==========================================================
721    // Utils
722    private static String addressForTag(AudioMix mix) {
723        return "addr=" + mix.getRegistration();
724    }
725
726    private void sendMsg(int msg) {
727        if (mEventHandler != null) {
728            mEventHandler.sendEmptyMessage(msg);
729        }
730    }
731
732    private void sendMsg(int msg, Object obj, int i) {
733        if (mEventHandler != null) {
734            mEventHandler.sendMessage(
735                    mEventHandler.obtainMessage(msg, i /*arg1*/, 0 /*arg2, ignored*/, obj));
736        }
737    }
738
739    private static IAudioService sService;
740
741    private static IAudioService getService()
742    {
743        if (sService != null) {
744            return sService;
745        }
746        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
747        sService = IAudioService.Stub.asInterface(b);
748        return sService;
749    }
750
751    public String toLogFriendlyString() {
752        String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
753        textDump += "config=" + mConfig.toLogFriendlyString();
754        return (textDump);
755    }
756
757    /** @hide */
758    @IntDef({
759        POLICY_STATUS_REGISTERED,
760        POLICY_STATUS_UNREGISTERED
761    })
762    @Retention(RetentionPolicy.SOURCE)
763    public @interface PolicyStatus {}
764}
765