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.Nullable;
22 import android.annotation.SystemApi;
23 import android.annotation.TestApi;
24 import android.app.ActivityManager;
25 import android.content.Context;
26 import android.content.pm.PackageManager;
27 import android.media.AudioAttributes;
28 import android.media.AudioDeviceInfo;
29 import android.media.AudioFocusInfo;
30 import android.media.AudioFormat;
31 import android.media.AudioManager;
32 import android.media.AudioRecord;
33 import android.media.AudioTrack;
34 import android.media.IAudioService;
35 import android.media.MediaRecorder;
36 import android.media.projection.MediaProjection;
37 import android.os.Binder;
38 import android.os.Handler;
39 import android.os.IBinder;
40 import android.os.Looper;
41 import android.os.Message;
42 import android.os.RemoteException;
43 import android.os.ServiceManager;
44 import android.util.Log;
45 import android.util.Slog;
46 
47 import com.android.internal.annotations.GuardedBy;
48 
49 import java.lang.annotation.Retention;
50 import java.lang.annotation.RetentionPolicy;
51 import java.lang.ref.WeakReference;
52 import java.util.ArrayList;
53 import java.util.List;
54 
55 /**
56  * @hide
57  * AudioPolicy provides access to the management of audio routing and audio focus.
58  */
59 @TestApi
60 @SystemApi
61 public class AudioPolicy {
62 
63     private static final String TAG = "AudioPolicy";
64     private static final boolean DEBUG = false;
65     private final Object mLock = new Object();
66 
67     /**
68      * The status of an audio policy that is valid but cannot be used because it is not registered.
69      */
70     public static final int POLICY_STATUS_UNREGISTERED = 1;
71     /**
72      * The status of an audio policy that is valid, successfully registered and thus active.
73      */
74     public static final int POLICY_STATUS_REGISTERED = 2;
75 
76     private int mStatus;
77     private String mRegistrationId;
78     private AudioPolicyStatusListener mStatusListener;
79     private boolean mIsFocusPolicy;
80     private boolean mIsTestFocusPolicy;
81 
82     /**
83      * The list of AudioTrack instances created to inject audio into the associated mixes
84      * Lazy initialization in {@link #createAudioTrackSource(AudioMix)}
85      */
86     @GuardedBy("mLock")
87     @Nullable private ArrayList<WeakReference<AudioTrack>> mInjectors;
88     /**
89      * The list AudioRecord instances created to capture audio from the associated mixes
90      * Lazy initialization in {@link #createAudioRecordSink(AudioMix)}
91      */
92     @GuardedBy("mLock")
93     @Nullable private ArrayList<WeakReference<AudioRecord>> mCaptors;
94 
95     /**
96      * The behavior of a policy with regards to audio focus where it relies on the application
97      * to do the ducking, the is the legacy and default behavior.
98      */
99     public static final int FOCUS_POLICY_DUCKING_IN_APP = 0;
100     public static final int FOCUS_POLICY_DUCKING_DEFAULT = FOCUS_POLICY_DUCKING_IN_APP;
101     /**
102      * The behavior of a policy with regards to audio focus where it handles ducking instead
103      * of the application losing focus and being signaled it can duck (as communicated by
104      * {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}).
105      * <br>Can only be used after having set a listener with
106      * {@link AudioPolicy#setAudioPolicyFocusListener(AudioPolicyFocusListener)}.
107      */
108     public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1;
109 
110     private AudioPolicyFocusListener mFocusListener;
111 
112     private final AudioPolicyVolumeCallback mVolCb;
113 
114     private Context mContext;
115 
116     private AudioPolicyConfig mConfig;
117 
118     private final MediaProjection mProjection;
119 
120     /** @hide */
getConfig()121     public AudioPolicyConfig getConfig() { return mConfig; }
122     /** @hide */
hasFocusListener()123     public boolean hasFocusListener() { return mFocusListener != null; }
124     /** @hide */
isFocusPolicy()125     public boolean isFocusPolicy() { return mIsFocusPolicy; }
126     /** @hide */
isTestFocusPolicy()127     public boolean isTestFocusPolicy() {
128         return mIsTestFocusPolicy;
129     }
130     /** @hide */
isVolumeController()131     public boolean isVolumeController() { return mVolCb != null; }
132     /** @hide */
getMediaProjection()133     public @Nullable MediaProjection getMediaProjection() {
134         return mProjection;
135     }
136 
137     /**
138      * The parameters are guaranteed non-null through the Builder
139      */
AudioPolicy(AudioPolicyConfig config, Context context, Looper looper, AudioPolicyFocusListener fl, AudioPolicyStatusListener sl, boolean isFocusPolicy, boolean isTestFocusPolicy, AudioPolicyVolumeCallback vc, @Nullable MediaProjection projection)140     private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper,
141             AudioPolicyFocusListener fl, AudioPolicyStatusListener sl,
142             boolean isFocusPolicy, boolean isTestFocusPolicy,
143             AudioPolicyVolumeCallback vc, @Nullable MediaProjection projection) {
144         mConfig = config;
145         mStatus = POLICY_STATUS_UNREGISTERED;
146         mContext = context;
147         if (looper == null) {
148             looper = Looper.getMainLooper();
149         }
150         if (looper != null) {
151             mEventHandler = new EventHandler(this, looper);
152         } else {
153             mEventHandler = null;
154             Log.e(TAG, "No event handler due to looper without a thread");
155         }
156         mFocusListener = fl;
157         mStatusListener = sl;
158         mIsFocusPolicy = isFocusPolicy;
159         mIsTestFocusPolicy = isTestFocusPolicy;
160         mVolCb = vc;
161         mProjection = projection;
162     }
163 
164     /**
165      * Builder class for {@link AudioPolicy} objects.
166      * By default the policy to be created doesn't govern audio focus decisions.
167      */
168     public static class Builder {
169         private ArrayList<AudioMix> mMixes;
170         private Context mContext;
171         private Looper mLooper;
172         private AudioPolicyFocusListener mFocusListener;
173         private AudioPolicyStatusListener mStatusListener;
174         private boolean mIsFocusPolicy = false;
175         private boolean mIsTestFocusPolicy = false;
176         private AudioPolicyVolumeCallback mVolCb;
177         private MediaProjection mProjection;
178 
179         /**
180          * Constructs a new Builder with no audio mixes.
181          * @param context the context for the policy
182          */
Builder(Context context)183         public Builder(Context context) {
184             mMixes = new ArrayList<AudioMix>();
185             mContext = context;
186         }
187 
188         /**
189          * Add an {@link AudioMix} to be part of the audio policy being built.
190          * @param mix a non-null {@link AudioMix} to be part of the audio policy.
191          * @return the same Builder instance.
192          * @throws IllegalArgumentException
193          */
194         @NonNull
addMix(@onNull AudioMix mix)195         public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
196             if (mix == null) {
197                 throw new IllegalArgumentException("Illegal null AudioMix argument");
198             }
199             mMixes.add(mix);
200             return this;
201         }
202 
203         /**
204          * Sets the {@link Looper} on which to run the event loop.
205          * @param looper a non-null specific Looper.
206          * @return the same Builder instance.
207          * @throws IllegalArgumentException
208          */
209         @NonNull
setLooper(@onNull Looper looper)210         public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
211             if (looper == null) {
212                 throw new IllegalArgumentException("Illegal null Looper argument");
213             }
214             mLooper = looper;
215             return this;
216         }
217 
218         /**
219          * Sets the audio focus listener for the policy.
220          * @param l a {@link AudioPolicy.AudioPolicyFocusListener}
221          */
setAudioPolicyFocusListener(AudioPolicyFocusListener l)222         public void setAudioPolicyFocusListener(AudioPolicyFocusListener l) {
223             mFocusListener = l;
224         }
225 
226         /**
227          * Declares whether this policy will grant and deny audio focus through
228          * the {@link AudioPolicy.AudioPolicyFocusListener}.
229          * If set to {@code true}, it is mandatory to set an
230          * {@link AudioPolicy.AudioPolicyFocusListener} in order to successfully build
231          * an {@code AudioPolicy} instance.
232          * @param enforce true if the policy will govern audio focus decisions.
233          * @return the same Builder instance.
234          */
235         @NonNull
setIsAudioFocusPolicy(boolean isFocusPolicy)236         public Builder setIsAudioFocusPolicy(boolean isFocusPolicy) {
237             mIsFocusPolicy = isFocusPolicy;
238             return this;
239         }
240 
241         /**
242          * @hide
243          * Test method to declare whether this audio focus policy is for test purposes only.
244          * Having a test policy registered will disable the current focus policy and replace it
245          * with this test policy. When unregistered, the previous focus policy will be restored.
246          * <p>A value of <code>true</code> will be ignored if the AudioPolicy is not also
247          * focus policy.
248          * @param isTestFocusPolicy true if the focus policy to register is for testing purposes.
249          * @return the same Builder instance
250          */
251         @TestApi
252         @NonNull
setIsTestFocusPolicy(boolean isTestFocusPolicy)253         public Builder setIsTestFocusPolicy(boolean isTestFocusPolicy) {
254             mIsTestFocusPolicy = isTestFocusPolicy;
255             return this;
256         }
257 
258         /**
259          * Sets the audio policy status listener.
260          * @param l a {@link AudioPolicy.AudioPolicyStatusListener}
261          */
setAudioPolicyStatusListener(AudioPolicyStatusListener l)262         public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
263             mStatusListener = l;
264         }
265 
266         /**
267          * Sets the callback to receive all volume key-related events.
268          * The callback will only be called if the device is configured to handle volume events
269          * in the PhoneWindowManager (see config_handleVolumeKeysInWindowManager)
270          * @param vc
271          * @return the same Builder instance.
272          */
273         @NonNull
setAudioPolicyVolumeCallback(@onNull AudioPolicyVolumeCallback vc)274         public Builder setAudioPolicyVolumeCallback(@NonNull AudioPolicyVolumeCallback vc) {
275             if (vc == null) {
276                 throw new IllegalArgumentException("Invalid null volume callback");
277             }
278             mVolCb = vc;
279             return this;
280         }
281 
282         /**
283          * Set a media projection obtained through createMediaProjection().
284          *
285          * A MediaProjection that can project audio allows to register an audio
286          * policy LOOPBACK|RENDER without the MODIFY_AUDIO_ROUTING permission.
287          *
288          * @hide
289          */
290         @NonNull
setMediaProjection(@onNull MediaProjection projection)291         public Builder setMediaProjection(@NonNull MediaProjection projection) {
292             if (projection == null) {
293                 throw new IllegalArgumentException("Invalid null volume callback");
294             }
295             mProjection = projection;
296             return this;
297 
298         }
299 
300         /**
301          * Combines all of the attributes that have been set on this {@code Builder} and returns a
302          * new {@link AudioPolicy} object.
303          * @return a new {@code AudioPolicy} object.
304          * @throws IllegalStateException if there is no
305          *     {@link AudioPolicy.AudioPolicyStatusListener} but the policy was configured
306          *     as an audio focus policy with {@link #setIsAudioFocusPolicy(boolean)}.
307          */
308         @NonNull
build()309         public AudioPolicy build() {
310             if (mStatusListener != null) {
311                 // the AudioPolicy status listener includes updates on each mix activity state
312                 for (AudioMix mix : mMixes) {
313                     mix.mCallbackFlags |= AudioMix.CALLBACK_FLAG_NOTIFY_ACTIVITY;
314                 }
315             }
316             if (mIsFocusPolicy && mFocusListener == null) {
317                 throw new IllegalStateException("Cannot be a focus policy without "
318                         + "an AudioPolicyFocusListener");
319             }
320             return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper,
321                     mFocusListener, mStatusListener, mIsFocusPolicy, mIsTestFocusPolicy,
322                     mVolCb, mProjection);
323         }
324     }
325 
326     /**
327      * Update the current configuration of the set of audio mixes by adding new ones, while
328      * keeping the policy registered.
329      * This method can only be called on a registered policy.
330      * @param mixes the list of {@link AudioMix} to add
331      * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR}
332      *    otherwise.
333      */
attachMixes(@onNull List<AudioMix> mixes)334     public int attachMixes(@NonNull List<AudioMix> mixes) {
335         if (mixes == null) {
336             throw new IllegalArgumentException("Illegal null list of AudioMix");
337         }
338         synchronized (mLock) {
339             if (mStatus != POLICY_STATUS_REGISTERED) {
340                 throw new IllegalStateException("Cannot alter unregistered AudioPolicy");
341             }
342             final ArrayList<AudioMix> zeMixes = new ArrayList<AudioMix>(mixes.size());
343             for (AudioMix mix : mixes) {
344                 if (mix == null) {
345                     throw new IllegalArgumentException("Illegal null AudioMix in attachMixes");
346                 } else {
347                     zeMixes.add(mix);
348                 }
349             }
350             final AudioPolicyConfig cfg = new AudioPolicyConfig(zeMixes);
351             IAudioService service = getService();
352             try {
353                 final int status = service.addMixForPolicy(cfg, this.cb());
354                 if (status == AudioManager.SUCCESS) {
355                     mConfig.add(zeMixes);
356                 }
357                 return status;
358             } catch (RemoteException e) {
359                 Log.e(TAG, "Dead object in attachMixes", e);
360                 return AudioManager.ERROR;
361             }
362         }
363     }
364 
365     /**
366      * Update the current configuration of the set of audio mixes by removing some, while
367      * keeping the policy registered.
368      * This method can only be called on a registered policy.
369      * @param mixes the list of {@link AudioMix} to remove
370      * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR}
371      *    otherwise.
372      */
detachMixes(@onNull List<AudioMix> mixes)373     public int detachMixes(@NonNull List<AudioMix> mixes) {
374         if (mixes == null) {
375             throw new IllegalArgumentException("Illegal null list of AudioMix");
376         }
377         synchronized (mLock) {
378             if (mStatus != POLICY_STATUS_REGISTERED) {
379                 throw new IllegalStateException("Cannot alter unregistered AudioPolicy");
380             }
381             final ArrayList<AudioMix> zeMixes = new ArrayList<AudioMix>(mixes.size());
382             for (AudioMix mix : mixes) {
383                 if (mix == null) {
384                     throw new IllegalArgumentException("Illegal null AudioMix in detachMixes");
385                     // TODO also check mix is currently contained in list of mixes
386                 } else {
387                     zeMixes.add(mix);
388                 }
389             }
390             final AudioPolicyConfig cfg = new AudioPolicyConfig(zeMixes);
391             IAudioService service = getService();
392             try {
393                 final int status = service.removeMixForPolicy(cfg, this.cb());
394                 if (status == AudioManager.SUCCESS) {
395                     mConfig.remove(zeMixes);
396                 }
397                 return status;
398             } catch (RemoteException e) {
399                 Log.e(TAG, "Dead object in detachMixes", e);
400                 return AudioManager.ERROR;
401             }
402         }
403     }
404 
405     /**
406      * @hide
407      * Configures the audio framework so that all audio stream originating from the given UID
408      * can only come from a set of audio devices.
409      * For this routing to be operational, a number of {@link AudioMix} instances must have been
410      * previously registered on this policy, and routed to a super-set of the given audio devices
411      * with {@link AudioMix.Builder#setDevice(android.media.AudioDeviceInfo)}. Note that having
412      * multiple devices in the list doesn't imply the signals will be duplicated on the different
413      * audio devices, final routing will depend on the {@link AudioAttributes} of the sounds being
414      * played.
415      * @param uid UID of the application to affect.
416      * @param devices list of devices to which the audio stream of the application may be routed.
417      * @return true if the change was successful, false otherwise.
418      */
419     @TestApi
420     @SystemApi
setUidDeviceAffinity(int uid, @NonNull List<AudioDeviceInfo> devices)421     public boolean setUidDeviceAffinity(int uid, @NonNull List<AudioDeviceInfo> devices) {
422         if (devices == null) {
423             throw new IllegalArgumentException("Illegal null list of audio devices");
424         }
425         synchronized (mLock) {
426             if (mStatus != POLICY_STATUS_REGISTERED) {
427                 throw new IllegalStateException("Cannot use unregistered AudioPolicy");
428             }
429             final int[] deviceTypes = new int[devices.size()];
430             final String[] deviceAdresses = new String[devices.size()];
431             int i = 0;
432             for (AudioDeviceInfo device : devices) {
433                 if (device == null) {
434                     throw new IllegalArgumentException(
435                             "Illegal null AudioDeviceInfo in setUidDeviceAffinity");
436                 }
437                 deviceTypes[i] =
438                         AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType());
439                 deviceAdresses[i] = device.getAddress();
440                 i++;
441             }
442             final IAudioService service = getService();
443             try {
444                 final int status = service.setUidDeviceAffinity(this.cb(),
445                         uid, deviceTypes, deviceAdresses);
446                 return (status == AudioManager.SUCCESS);
447             } catch (RemoteException e) {
448                 Log.e(TAG, "Dead object in setUidDeviceAffinity", e);
449                 return false;
450             }
451         }
452     }
453 
454     /**
455      * @hide
456      * Removes audio device affinity previously set by
457      * {@link #setUidDeviceAffinity(int, java.util.List)}.
458      * @param uid UID of the application affected.
459      * @return true if the change was successful, false otherwise.
460      */
461     @TestApi
462     @SystemApi
removeUidDeviceAffinity(int uid)463     public boolean removeUidDeviceAffinity(int uid) {
464         synchronized (mLock) {
465             if (mStatus != POLICY_STATUS_REGISTERED) {
466                 throw new IllegalStateException("Cannot use unregistered AudioPolicy");
467             }
468             final IAudioService service = getService();
469             try {
470                 final int status = service.removeUidDeviceAffinity(this.cb(), uid);
471                 return (status == AudioManager.SUCCESS);
472             } catch (RemoteException e) {
473                 Log.e(TAG, "Dead object in removeUidDeviceAffinity", e);
474                 return false;
475             }
476         }
477     }
478 
setRegistration(String regId)479     public void setRegistration(String regId) {
480         synchronized (mLock) {
481             mRegistrationId = regId;
482             mConfig.setRegistration(regId);
483             if (regId != null) {
484                 mStatus = POLICY_STATUS_REGISTERED;
485             } else {
486                 mStatus = POLICY_STATUS_UNREGISTERED;
487             }
488         }
489         sendMsg(MSG_POLICY_STATUS_CHANGE);
490     }
491 
policyReadyToUse()492     private boolean policyReadyToUse() {
493         synchronized (mLock) {
494             if (mStatus != POLICY_STATUS_REGISTERED) {
495                 Log.e(TAG, "Cannot use unregistered AudioPolicy");
496                 return false;
497             }
498             if (mRegistrationId == null) {
499                 Log.e(TAG, "Cannot use unregistered AudioPolicy");
500                 return false;
501             }
502         }
503 
504         // Loopback|capture only need an audio projection, everything else need MODIFY_AUDIO_ROUTING
505         boolean canModifyAudioRouting = PackageManager.PERMISSION_GRANTED
506                 == checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
507 
508         boolean canProjectAudio;
509         try {
510             canProjectAudio = mProjection != null && mProjection.getProjection().canProjectAudio();
511         } catch (RemoteException e) {
512             Log.e(TAG, "Failed to check if MediaProjection#canProjectAudio");
513             throw e.rethrowFromSystemServer();
514         }
515 
516         if (!((isLoopbackRenderPolicy() && canProjectAudio) || canModifyAudioRouting)) {
517             Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid "
518                     + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING or "
519                     + "MediaProjection that can project audio.");
520             return false;
521         }
522         return true;
523     }
524 
isLoopbackRenderPolicy()525     private boolean isLoopbackRenderPolicy() {
526         synchronized (mLock) {
527             return mConfig.mMixes.stream().allMatch(mix -> mix.getRouteFlags()
528                     == (mix.ROUTE_FLAG_RENDER | mix.ROUTE_FLAG_LOOP_BACK));
529         }
530     }
531 
532     /**
533      * Returns {@link PackageManager#PERMISSION_GRANTED} if the caller has the given permission.
534      */
checkCallingOrSelfPermission(String permission)535     private @PackageManager.PermissionResult int checkCallingOrSelfPermission(String permission) {
536         if (mContext != null) {
537             return mContext.checkCallingOrSelfPermission(permission);
538         }
539         Slog.v(TAG, "Null context, checking permission via ActivityManager");
540         int pid = Binder.getCallingPid();
541         int uid = Binder.getCallingUid();
542         try {
543             return ActivityManager.getService().checkPermission(permission, pid, uid);
544         } catch (RemoteException e) {
545             throw e.rethrowFromSystemServer();
546         }
547     }
548 
checkMixReadyToUse(AudioMix mix, boolean forTrack)549     private void checkMixReadyToUse(AudioMix mix, boolean forTrack)
550             throws IllegalArgumentException{
551         if (mix == null) {
552             String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation"
553                     : "Invalid null AudioMix for AudioRecord creation";
554             throw new IllegalArgumentException(msg);
555         }
556         if (!mConfig.mMixes.contains(mix)) {
557             throw new IllegalArgumentException("Invalid mix: not part of this policy");
558         }
559         if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK)
560         {
561             throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back");
562         }
563         if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) {
564             throw new IllegalArgumentException(
565                     "Invalid AudioMix: not defined for being a recording source");
566         }
567         if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) {
568             throw new IllegalArgumentException(
569                     "Invalid AudioMix: not defined for capturing playback");
570         }
571     }
572 
573     /**
574      * Returns the current behavior for audio focus-related ducking.
575      * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
576      */
getFocusDuckingBehavior()577     public int getFocusDuckingBehavior() {
578         return mConfig.mDuckingPolicy;
579     }
580 
581     // Note on implementation: not part of the Builder as there can be only one registered policy
582     // that handles ducking but there can be multiple policies
583     /**
584      * Sets the behavior for audio focus-related ducking.
585      * There must be a focus listener if this policy is to handle ducking.
586      * @param behavior {@link #FOCUS_POLICY_DUCKING_IN_APP} or
587      *     {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
588      * @return {@link AudioManager#SUCCESS} or {@link AudioManager#ERROR} (for instance if there
589      *     is already an audio policy that handles ducking).
590      * @throws IllegalArgumentException
591      * @throws IllegalStateException
592      */
setFocusDuckingBehavior(int behavior)593     public int setFocusDuckingBehavior(int behavior)
594             throws IllegalArgumentException, IllegalStateException {
595         if ((behavior != FOCUS_POLICY_DUCKING_IN_APP)
596                 && (behavior != FOCUS_POLICY_DUCKING_IN_POLICY)) {
597             throw new IllegalArgumentException("Invalid ducking behavior " + behavior);
598         }
599         synchronized (mLock) {
600             if (mStatus != POLICY_STATUS_REGISTERED) {
601                 throw new IllegalStateException(
602                         "Cannot change ducking behavior for unregistered policy");
603             }
604             if ((behavior == FOCUS_POLICY_DUCKING_IN_POLICY)
605                     && (mFocusListener == null)) {
606                 // there must be a focus listener if the policy handles ducking
607                 throw new IllegalStateException(
608                         "Cannot handle ducking without an audio focus listener");
609             }
610             IAudioService service = getService();
611             try {
612                 final int status = service.setFocusPropertiesForPolicy(behavior /*duckingBehavior*/,
613                         this.cb());
614                 if (status == AudioManager.SUCCESS) {
615                     mConfig.mDuckingPolicy = behavior;
616                 }
617                 return status;
618             } catch (RemoteException e) {
619                 Log.e(TAG, "Dead object in setFocusPropertiesForPolicy for behavior", e);
620                 return AudioManager.ERROR;
621             }
622         }
623     }
624 
625     /**
626      * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
627      * Audio buffers recorded through the created instance will contain the mix of the audio
628      * streams that fed the given mixer.
629      * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
630      *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
631      * @return a new {@link AudioRecord} instance whose data format is the one defined in the
632      *     {@link AudioMix}, or null if this policy was not successfully registered
633      *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
634      * @throws IllegalArgumentException
635      */
createAudioRecordSink(AudioMix mix)636     public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException {
637         if (!policyReadyToUse()) {
638             Log.e(TAG, "Cannot create AudioRecord sink for AudioMix");
639             return null;
640         }
641         checkMixReadyToUse(mix, false/*not for an AudioTrack*/);
642         // create an AudioFormat from the mix format compatible with recording, as the mix
643         // was defined for playback
644         AudioFormat mixFormat = new AudioFormat.Builder(mix.getFormat())
645                 .setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask(
646                         mix.getFormat().getChannelMask()))
647                 .build();
648         // create the AudioRecord, configured for loop back, using the same format as the mix
649         AudioRecord ar = new AudioRecord(
650                 new AudioAttributes.Builder()
651                         .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
652                         .addTag(addressForTag(mix))
653                         .addTag(AudioRecord.SUBMIX_FIXED_VOLUME)
654                         .build(),
655                 mixFormat,
656                 AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
657                         // using stereo for buffer size to avoid the current poor support for masks
658                         AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()),
659                 AudioManager.AUDIO_SESSION_ID_GENERATE
660                 );
661         synchronized (mLock) {
662             if (mCaptors == null) {
663                 mCaptors = new ArrayList<>(1);
664             }
665             mCaptors.add(new WeakReference<AudioRecord>(ar));
666         }
667         return ar;
668     }
669 
670     /**
671      * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}.
672      * Audio buffers played through the created instance will be sent to the given mix
673      * to be recorded through the recording APIs.
674      * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
675      *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
676      * @return a new {@link AudioTrack} instance whose data format is the one defined in the
677      *     {@link AudioMix}, or null if this policy was not successfully registered
678      *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
679      * @throws IllegalArgumentException
680      */
createAudioTrackSource(AudioMix mix)681     public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException {
682         if (!policyReadyToUse()) {
683             Log.e(TAG, "Cannot create AudioTrack source for AudioMix");
684             return null;
685         }
686         checkMixReadyToUse(mix, true/*for an AudioTrack*/);
687         // create the AudioTrack, configured for loop back, using the same format as the mix
688         AudioTrack at = new AudioTrack(
689                 new AudioAttributes.Builder()
690                         .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
691                         .addTag(addressForTag(mix))
692                         .build(),
693                 mix.getFormat(),
694                 AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
695                         mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()),
696                 AudioTrack.MODE_STREAM,
697                 AudioManager.AUDIO_SESSION_ID_GENERATE
698                 );
699         synchronized (mLock) {
700             if (mInjectors == null) {
701                 mInjectors = new ArrayList<>(1);
702             }
703             mInjectors.add(new WeakReference<AudioTrack>(at));
704         }
705         return at;
706     }
707 
708     /**
709      * @hide
710      */
invalidateCaptorsAndInjectors()711     public void invalidateCaptorsAndInjectors() {
712         if (!policyReadyToUse()) {
713             return;
714         }
715         synchronized (mLock) {
716             if (mInjectors != null) {
717                 for (final WeakReference<AudioTrack> weakTrack : mInjectors) {
718                     final AudioTrack track = weakTrack.get();
719                     if (track == null) {
720                         break;
721                     }
722                     // TODO: add synchronous versions
723                     track.stop();
724                     track.flush();
725                 }
726             }
727             if (mCaptors != null) {
728                 for (final WeakReference<AudioRecord> weakRecord : mCaptors) {
729                     final AudioRecord record = weakRecord.get();
730                     if (record == null) {
731                         break;
732                     }
733                     // TODO: if needed: implement an invalidate method
734                     record.stop();
735                 }
736             }
737         }
738     }
739 
getStatus()740     public int getStatus() {
741         return mStatus;
742     }
743 
744     public static abstract class AudioPolicyStatusListener {
onStatusChange()745         public void onStatusChange() {}
onMixStateUpdate(AudioMix mix)746         public void onMixStateUpdate(AudioMix mix) {}
747     }
748 
749     public static abstract class AudioPolicyFocusListener {
onAudioFocusGrant(AudioFocusInfo afi, int requestResult)750         public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {}
onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified)751         public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {}
752         /**
753          * Called whenever an application requests audio focus.
754          * Only ever called if the {@link AudioPolicy} was built with
755          * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}.
756          * @param afi information about the focus request and the requester
757          * @param requestResult deprecated after the addition of
758          *     {@link AudioManager#setFocusRequestResult(AudioFocusInfo, int, AudioPolicy)}
759          *     in Android P, always equal to {@link #AUDIOFOCUS_REQUEST_GRANTED}.
760          */
onAudioFocusRequest(AudioFocusInfo afi, int requestResult)761         public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {}
762         /**
763          * Called whenever an application abandons audio focus.
764          * Only ever called if the {@link AudioPolicy} was built with
765          * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}.
766          * @param afi information about the focus request being abandoned and the original
767          *     requester.
768          */
onAudioFocusAbandon(AudioFocusInfo afi)769         public void onAudioFocusAbandon(AudioFocusInfo afi) {}
770     }
771 
772     /**
773      * Callback class to receive volume change-related events.
774      * See {@link #Builder.setAudioPolicyVolumeCallback(AudioPolicyCallback)} to configure the
775      * {@link AudioPolicy} to receive those events.
776      *
777      */
778     public static abstract class AudioPolicyVolumeCallback {
AudioPolicyVolumeCallback()779         public AudioPolicyVolumeCallback() {}
780         /**
781          * Called when volume key-related changes are triggered, on the key down event.
782          * @param adjustment the type of volume adjustment for the key.
783          */
onVolumeAdjustment(@udioManager.VolumeAdjustment int adjustment)784         public void onVolumeAdjustment(@AudioManager.VolumeAdjustment int adjustment) {}
785     }
786 
onPolicyStatusChange()787     private void onPolicyStatusChange() {
788         AudioPolicyStatusListener l;
789         synchronized (mLock) {
790             if (mStatusListener == null) {
791                 return;
792             }
793             l = mStatusListener;
794         }
795         l.onStatusChange();
796     }
797 
798     //==================================================
799     // Callback interface
800 
801     /** @hide */
cb()802     public IAudioPolicyCallback cb() { return mPolicyCb; }
803 
804     private final IAudioPolicyCallback mPolicyCb = new IAudioPolicyCallback.Stub() {
805 
806         public void notifyAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
807             sendMsg(MSG_FOCUS_GRANT, afi, requestResult);
808             if (DEBUG) {
809                 Log.v(TAG, "notifyAudioFocusGrant: pack=" + afi.getPackageName() + " client="
810                         + afi.getClientId() + "reqRes=" + requestResult);
811             }
812         }
813 
814         public void notifyAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
815             sendMsg(MSG_FOCUS_LOSS, afi, wasNotified ? 1 : 0);
816             if (DEBUG) {
817                 Log.v(TAG, "notifyAudioFocusLoss: pack=" + afi.getPackageName() + " client="
818                         + afi.getClientId() + "wasNotified=" + wasNotified);
819             }
820         }
821 
822         public void notifyAudioFocusRequest(AudioFocusInfo afi, int requestResult) {
823             sendMsg(MSG_FOCUS_REQUEST, afi, requestResult);
824             if (DEBUG) {
825                 Log.v(TAG, "notifyAudioFocusRequest: pack=" + afi.getPackageName() + " client="
826                         + afi.getClientId() + " gen=" + afi.getGen());
827             }
828         }
829 
830         public void notifyAudioFocusAbandon(AudioFocusInfo afi) {
831             sendMsg(MSG_FOCUS_ABANDON, afi, 0 /* ignored */);
832             if (DEBUG) {
833                 Log.v(TAG, "notifyAudioFocusAbandon: pack=" + afi.getPackageName() + " client="
834                         + afi.getClientId());
835             }
836         }
837 
838         public void notifyMixStateUpdate(String regId, int state) {
839             for (AudioMix mix : mConfig.getMixes()) {
840                 if (mix.getRegistration().equals(regId)) {
841                     mix.mMixState = state;
842                     sendMsg(MSG_MIX_STATE_UPDATE, mix, 0/*ignored*/);
843                     if (DEBUG) {
844                         Log.v(TAG, "notifyMixStateUpdate: regId=" + regId + " state=" + state);
845                     }
846                 }
847             }
848         }
849 
850         public void notifyVolumeAdjust(int adjustment) {
851             sendMsg(MSG_VOL_ADJUST, null /* ignored */, adjustment);
852             if (DEBUG) {
853                 Log.v(TAG, "notifyVolumeAdjust: " + adjustment);
854             }
855         }
856 
857         public void notifyUnregistration() {
858             setRegistration(null);
859         }
860     };
861 
862     //==================================================
863     // Event handling
864     private final EventHandler mEventHandler;
865     private final static int MSG_POLICY_STATUS_CHANGE = 0;
866     private final static int MSG_FOCUS_GRANT = 1;
867     private final static int MSG_FOCUS_LOSS = 2;
868     private final static int MSG_MIX_STATE_UPDATE = 3;
869     private final static int MSG_FOCUS_REQUEST = 4;
870     private final static int MSG_FOCUS_ABANDON = 5;
871     private final static int MSG_VOL_ADJUST = 6;
872 
873     private class EventHandler extends Handler {
EventHandler(AudioPolicy ap, Looper looper)874         public EventHandler(AudioPolicy ap, Looper looper) {
875             super(looper);
876         }
877 
878         @Override
handleMessage(Message msg)879         public void handleMessage(Message msg) {
880             switch(msg.what) {
881                 case MSG_POLICY_STATUS_CHANGE:
882                     onPolicyStatusChange();
883                     break;
884                 case MSG_FOCUS_GRANT:
885                     if (mFocusListener != null) {
886                         mFocusListener.onAudioFocusGrant(
887                                 (AudioFocusInfo) msg.obj, msg.arg1);
888                     }
889                     break;
890                 case MSG_FOCUS_LOSS:
891                     if (mFocusListener != null) {
892                         mFocusListener.onAudioFocusLoss(
893                                 (AudioFocusInfo) msg.obj, msg.arg1 != 0);
894                     }
895                     break;
896                 case MSG_MIX_STATE_UPDATE:
897                     if (mStatusListener != null) {
898                         mStatusListener.onMixStateUpdate((AudioMix) msg.obj);
899                     }
900                     break;
901                 case MSG_FOCUS_REQUEST:
902                     if (mFocusListener != null) {
903                         mFocusListener.onAudioFocusRequest((AudioFocusInfo) msg.obj, msg.arg1);
904                     } else { // should never be null, but don't crash
905                         Log.e(TAG, "Invalid null focus listener for focus request event");
906                     }
907                     break;
908                 case MSG_FOCUS_ABANDON:
909                     if (mFocusListener != null) { // should never be null
910                         mFocusListener.onAudioFocusAbandon((AudioFocusInfo) msg.obj);
911                     } else { // should never be null, but don't crash
912                         Log.e(TAG, "Invalid null focus listener for focus abandon event");
913                     }
914                     break;
915                 case MSG_VOL_ADJUST:
916                     if (mVolCb != null) {
917                         mVolCb.onVolumeAdjustment(msg.arg1);
918                     } else { // should never be null, but don't crash
919                         Log.e(TAG, "Invalid null volume event");
920                     }
921                     break;
922                 default:
923                     Log.e(TAG, "Unknown event " + msg.what);
924             }
925         }
926     }
927 
928     //==========================================================
929     // Utils
addressForTag(AudioMix mix)930     private static String addressForTag(AudioMix mix) {
931         return "addr=" + mix.getRegistration();
932     }
933 
sendMsg(int msg)934     private void sendMsg(int msg) {
935         if (mEventHandler != null) {
936             mEventHandler.sendEmptyMessage(msg);
937         }
938     }
939 
sendMsg(int msg, Object obj, int i)940     private void sendMsg(int msg, Object obj, int i) {
941         if (mEventHandler != null) {
942             mEventHandler.sendMessage(
943                     mEventHandler.obtainMessage(msg, i /*arg1*/, 0 /*arg2, ignored*/, obj));
944         }
945     }
946 
947     private static IAudioService sService;
948 
getService()949     private static IAudioService getService()
950     {
951         if (sService != null) {
952             return sService;
953         }
954         IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
955         sService = IAudioService.Stub.asInterface(b);
956         return sService;
957     }
958 
toLogFriendlyString()959     public String toLogFriendlyString() {
960         String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
961         textDump += "config=" + mConfig.toLogFriendlyString();
962         return (textDump);
963     }
964 
965     /** @hide */
966     @IntDef({
967         POLICY_STATUS_REGISTERED,
968         POLICY_STATUS_UNREGISTERED
969     })
970     @Retention(RetentionPolicy.SOURCE)
971     public @interface PolicyStatus {}
972 }
973