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 
17 package android.media.audiopolicy;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.SystemApi;
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.media.AudioAttributes;
25 import android.media.AudioFocusInfo;
26 import android.media.AudioFormat;
27 import android.media.AudioManager;
28 import android.media.AudioRecord;
29 import android.media.AudioTrack;
30 import android.media.IAudioService;
31 import android.media.MediaRecorder;
32 import android.os.Binder;
33 import android.os.Handler;
34 import android.os.IBinder;
35 import android.os.Looper;
36 import android.os.Message;
37 import android.os.RemoteException;
38 import android.os.ServiceManager;
39 import android.util.Log;
40 import android.util.Slog;
41 
42 import java.lang.annotation.Retention;
43 import java.lang.annotation.RetentionPolicy;
44 import java.util.ArrayList;
45 
46 /**
47  * @hide
48  * AudioPolicy provides access to the management of audio routing and audio focus.
49  */
50 @SystemApi
51 public 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 */
getConfig()96     public AudioPolicyConfig getConfig() { return mConfig; }
97     /** @hide */
hasFocusListener()98     public boolean hasFocusListener() { return mFocusListener != null; }
99 
100     /**
101      * The parameter is guaranteed non-null through the Builder
102      */
AudioPolicy(AudioPolicyConfig config, Context context, Looper looper, AudioPolicyFocusListener fl, AudioPolicyStatusListener sl)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
Builder(Context context)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
addMix(@onNull AudioMix mix)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
setLooper(@onNull Looper looper)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
setAudioPolicyFocusListener(AudioPolicyFocusListener l)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
setAudioPolicyStatusListener(AudioPolicyStatusListener l)186         public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
187             mStatusListener = l;
188         }
189 
190         @SystemApi
build()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 
setRegistration(String regId)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 
policyReadyToUse()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 
checkMixReadyToUse(AudioMix mix, boolean forTrack)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
getFocusDuckingBehavior()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
setFocusDuckingBehavior(int behavior)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
createAudioRecordSink(AudioMix mix)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
createAudioTrackSource(AudioMix mix)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
getStatus()391     public int getStatus() {
392         return mStatus;
393     }
394 
395     @SystemApi
396     public static abstract class AudioPolicyStatusListener {
onStatusChange()397         public void onStatusChange() {}
onMixStateUpdate(AudioMix mix)398         public void onMixStateUpdate(AudioMix mix) {}
399     }
400 
401     @SystemApi
402     public static abstract class AudioPolicyFocusListener {
onAudioFocusGrant(AudioFocusInfo afi, int requestResult)403         public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {}
onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified)404         public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {}
405     }
406 
onPolicyStatusChange()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 */
cb()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 {
EventHandler(AudioPolicy ap, Looper looper)464         public EventHandler(AudioPolicy ap, Looper looper) {
465             super(looper);
466         }
467 
468         @Override
handleMessage(Message msg)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
addressForTag(AudioMix mix)499     private static String addressForTag(AudioMix mix) {
500         return "addr=" + mix.getRegistration();
501     }
502 
sendMsg(int msg)503     private void sendMsg(int msg) {
504         if (mEventHandler != null) {
505             mEventHandler.sendEmptyMessage(msg);
506         }
507     }
508 
sendMsg(int msg, Object obj, int i)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 
getService()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 
toLogFriendlyString()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