Home | History | Annotate | Download | only in audiopolicy
      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 */
     96     public AudioPolicyConfig getConfig() { return mConfig; }
     97     /** @hide */
     98     public boolean hasFocusListener() { return mFocusListener != null; }
     99 
    100     /**
    101      * The parameter is guaranteed non-null through the Builder
    102      */
    103     private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper,
    104             AudioPolicyFocusListener fl, AudioPolicyStatusListener sl) {
    105         mConfig = config;
    106         mStatus = POLICY_STATUS_UNREGISTERED;
    107         mContext = context;
    108         if (looper == null) {
    109             looper = Looper.getMainLooper();
    110         }
    111         if (looper != null) {
    112             mEventHandler = new EventHandler(this, looper);
    113         } else {
    114             mEventHandler = null;
    115             Log.e(TAG, "No event handler due to looper without a thread");
    116         }
    117         mFocusListener = fl;
    118         mStatusListener = sl;
    119     }
    120 
    121     /**
    122      * Builder class for {@link AudioPolicy} objects
    123      */
    124     @SystemApi
    125     public static class Builder {
    126         private ArrayList<AudioMix> mMixes;
    127         private Context mContext;
    128         private Looper mLooper;
    129         private AudioPolicyFocusListener mFocusListener;
    130         private AudioPolicyStatusListener mStatusListener;
    131 
    132         /**
    133          * Constructs a new Builder with no audio mixes.
    134          * @param context the context for the policy
    135          */
    136         @SystemApi
    137         public Builder(Context context) {
    138             mMixes = new ArrayList<AudioMix>();
    139             mContext = context;
    140         }
    141 
    142         /**
    143          * Add an {@link AudioMix} to be part of the audio policy being built.
    144          * @param mix a non-null {@link AudioMix} to be part of the audio policy.
    145          * @return the same Builder instance.
    146          * @throws IllegalArgumentException
    147          */
    148         @SystemApi
    149         public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
    150             if (mix == null) {
    151                 throw new IllegalArgumentException("Illegal null AudioMix argument");
    152             }
    153             mMixes.add(mix);
    154             return this;
    155         }
    156 
    157         /**
    158          * Sets the {@link Looper} on which to run the event loop.
    159          * @param looper a non-null specific Looper.
    160          * @return the same Builder instance.
    161          * @throws IllegalArgumentException
    162          */
    163         @SystemApi
    164         public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
    165             if (looper == null) {
    166                 throw new IllegalArgumentException("Illegal null Looper argument");
    167             }
    168             mLooper = looper;
    169             return this;
    170         }
    171 
    172         /**
    173          * Sets the audio focus listener for the policy.
    174          * @param l a {@link AudioPolicy.AudioPolicyFocusListener}
    175          */
    176         @SystemApi
    177         public void setAudioPolicyFocusListener(AudioPolicyFocusListener l) {
    178             mFocusListener = l;
    179         }
    180 
    181         /**
    182          * Sets the audio policy status listener.
    183          * @param l a {@link AudioPolicy.AudioPolicyStatusListener}
    184          */
    185         @SystemApi
    186         public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
    187             mStatusListener = l;
    188         }
    189 
    190         @SystemApi
    191         public AudioPolicy build() {
    192             return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper,
    193                     mFocusListener, mStatusListener);
    194         }
    195     }
    196 
    197     public void setRegistration(String regId) {
    198         synchronized (mLock) {
    199             mRegistrationId = regId;
    200             mConfig.setRegistration(regId);
    201             if (regId != null) {
    202                 mStatus = POLICY_STATUS_REGISTERED;
    203             } else {
    204                 mStatus = POLICY_STATUS_UNREGISTERED;
    205             }
    206         }
    207         sendMsg(MSG_POLICY_STATUS_CHANGE);
    208     }
    209 
    210     private boolean policyReadyToUse() {
    211         synchronized (mLock) {
    212             if (mStatus != POLICY_STATUS_REGISTERED) {
    213                 Log.e(TAG, "Cannot use unregistered AudioPolicy");
    214                 return false;
    215             }
    216             if (mContext == null) {
    217                 Log.e(TAG, "Cannot use AudioPolicy without context");
    218                 return false;
    219             }
    220             if (mRegistrationId == null) {
    221                 Log.e(TAG, "Cannot use unregistered AudioPolicy");
    222                 return false;
    223             }
    224         }
    225         if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
    226                         android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
    227             Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid "
    228                     + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING");
    229             return false;
    230         }
    231         return true;
    232     }
    233 
    234     private void checkMixReadyToUse(AudioMix mix, boolean forTrack)
    235             throws IllegalArgumentException{
    236         if (mix == null) {
    237             String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation"
    238                     : "Invalid null AudioMix for AudioRecord creation";
    239             throw new IllegalArgumentException(msg);
    240         }
    241         if (!mConfig.mMixes.contains(mix)) {
    242             throw new IllegalArgumentException("Invalid mix: not part of this policy");
    243         }
    244         if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK)
    245         {
    246             throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back");
    247         }
    248         if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) {
    249             throw new IllegalArgumentException(
    250                     "Invalid AudioMix: not defined for being a recording source");
    251         }
    252         if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) {
    253             throw new IllegalArgumentException(
    254                     "Invalid AudioMix: not defined for capturing playback");
    255         }
    256     }
    257 
    258     /**
    259      * Returns the current behavior for audio focus-related ducking.
    260      * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
    261      */
    262     @SystemApi
    263     public int getFocusDuckingBehavior() {
    264         return mConfig.mDuckingPolicy;
    265     }
    266 
    267     // Note on implementation: not part of the Builder as there can be only one registered policy
    268     // that handles ducking but there can be multiple policies
    269     /**
    270      * Sets the behavior for audio focus-related ducking.
    271      * There must be a focus listener if this policy is to handle ducking.
    272      * @param behavior {@link #FOCUS_POLICY_DUCKING_IN_APP} or
    273      *     {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
    274      * @return {@link AudioManager#SUCCESS} or {@link AudioManager#ERROR} (for instance if there
    275      *     is already an audio policy that handles ducking).
    276      * @throws IllegalArgumentException
    277      * @throws IllegalStateException
    278      */
    279     @SystemApi
    280     public int setFocusDuckingBehavior(int behavior)
    281             throws IllegalArgumentException, IllegalStateException {
    282         if ((behavior != FOCUS_POLICY_DUCKING_IN_APP)
    283                 && (behavior != FOCUS_POLICY_DUCKING_IN_POLICY)) {
    284             throw new IllegalArgumentException("Invalid ducking behavior " + behavior);
    285         }
    286         synchronized (mLock) {
    287             if (mStatus != POLICY_STATUS_REGISTERED) {
    288                 throw new IllegalStateException(
    289                         "Cannot change ducking behavior for unregistered policy");
    290             }
    291             if ((behavior == FOCUS_POLICY_DUCKING_IN_POLICY)
    292                     && (mFocusListener == null)) {
    293                 // there must be a focus listener if the policy handles ducking
    294                 throw new IllegalStateException(
    295                         "Cannot handle ducking without an audio focus listener");
    296             }
    297             IAudioService service = getService();
    298             try {
    299                 final int status = service.setFocusPropertiesForPolicy(behavior /*duckingBehavior*/,
    300                         this.cb());
    301                 if (status == AudioManager.SUCCESS) {
    302                     mConfig.mDuckingPolicy = behavior;
    303                 }
    304                 return status;
    305             } catch (RemoteException e) {
    306                 Log.e(TAG, "Dead object in setFocusPropertiesForPolicy for behavior", e);
    307                 return AudioManager.ERROR;
    308             }
    309         }
    310     }
    311 
    312     /**
    313      * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
    314      * Audio buffers recorded through the created instance will contain the mix of the audio
    315      * streams that fed the given mixer.
    316      * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
    317      *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
    318      * @return a new {@link AudioRecord} instance whose data format is the one defined in the
    319      *     {@link AudioMix}, or null if this policy was not successfully registered
    320      *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
    321      * @throws IllegalArgumentException
    322      */
    323     @SystemApi
    324     public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException {
    325         if (!policyReadyToUse()) {
    326             Log.e(TAG, "Cannot create AudioRecord sink for AudioMix");
    327             return null;
    328         }
    329         checkMixReadyToUse(mix, false/*not for an AudioTrack*/);
    330         // create an AudioFormat from the mix format compatible with recording, as the mix
    331         // was defined for playback
    332         AudioFormat mixFormat = new AudioFormat.Builder(mix.getFormat())
    333                 .setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask(
    334                         mix.getFormat().getChannelMask()))
    335                 .build();
    336         // create the AudioRecord, configured for loop back, using the same format as the mix
    337         AudioRecord ar = new AudioRecord(
    338                 new AudioAttributes.Builder()
    339                         .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
    340                         .addTag(addressForTag(mix))
    341                         .build(),
    342                 mixFormat,
    343                 AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
    344                         // using stereo for buffer size to avoid the current poor support for masks
    345                         AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()),
    346                 AudioManager.AUDIO_SESSION_ID_GENERATE
    347                 );
    348         return ar;
    349     }
    350 
    351     /**
    352      * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}.
    353      * Audio buffers played through the created instance will be sent to the given mix
    354      * to be recorded through the recording APIs.
    355      * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
    356      *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
    357      * @return a new {@link AudioTrack} instance whose data format is the one defined in the
    358      *     {@link AudioMix}, or null if this policy was not successfully registered
    359      *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
    360      * @throws IllegalArgumentException
    361      */
    362     @SystemApi
    363     public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException {
    364         if (!policyReadyToUse()) {
    365             Log.e(TAG, "Cannot create AudioTrack source for AudioMix");
    366             return null;
    367         }
    368         checkMixReadyToUse(mix, true/*for an AudioTrack*/);
    369         // create the AudioTrack, configured for loop back, using the same format as the mix
    370         AudioTrack at = new AudioTrack(
    371                 new AudioAttributes.Builder()
    372                         .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
    373                         .addTag(addressForTag(mix))
    374                         .build(),
    375                 mix.getFormat(),
    376                 AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
    377                         mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()),
    378                 AudioTrack.MODE_STREAM,
    379                 AudioManager.AUDIO_SESSION_ID_GENERATE
    380                 );
    381         return at;
    382     }
    383 
    384     @SystemApi
    385     public int getStatus() {
    386         return mStatus;
    387     }
    388 
    389     @SystemApi
    390     public static abstract class AudioPolicyStatusListener {
    391         public void onStatusChange() {}
    392         public void onMixStateUpdate(AudioMix mix) {}
    393     }
    394 
    395     @SystemApi
    396     public static abstract class AudioPolicyFocusListener {
    397         public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {}
    398         public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {}
    399     }
    400 
    401     private void onPolicyStatusChange() {
    402         AudioPolicyStatusListener l;
    403         synchronized (mLock) {
    404             if (mStatusListener == null) {
    405                 return;
    406             }
    407             l = mStatusListener;
    408         }
    409         l.onStatusChange();
    410     }
    411 
    412     //==================================================
    413     // Callback interface
    414 
    415     /** @hide */
    416     public IAudioPolicyCallback cb() { return mPolicyCb; }
    417 
    418     private final IAudioPolicyCallback mPolicyCb = new IAudioPolicyCallback.Stub() {
    419 
    420         public void notifyAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
    421             sendMsg(MSG_FOCUS_GRANT, afi, requestResult);
    422             if (DEBUG) {
    423                 Log.v(TAG, "notifyAudioFocusGrant: pack=" + afi.getPackageName() + " client="
    424                         + afi.getClientId() + "reqRes=" + requestResult);
    425             }
    426         }
    427 
    428         public void notifyAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
    429             sendMsg(MSG_FOCUS_LOSS, afi, wasNotified ? 1 : 0);
    430             if (DEBUG) {
    431                 Log.v(TAG, "notifyAudioFocusLoss: pack=" + afi.getPackageName() + " client="
    432                         + afi.getClientId() + "wasNotified=" + wasNotified);
    433             }
    434         }
    435     };
    436 
    437     //==================================================
    438     // Event handling
    439     private final EventHandler mEventHandler;
    440     private final static int MSG_POLICY_STATUS_CHANGE = 0;
    441     private final static int MSG_FOCUS_GRANT = 1;
    442     private final static int MSG_FOCUS_LOSS = 2;
    443 
    444     private class EventHandler extends Handler {
    445         public EventHandler(AudioPolicy ap, Looper looper) {
    446             super(looper);
    447         }
    448 
    449         @Override
    450         public void handleMessage(Message msg) {
    451             switch(msg.what) {
    452                 case MSG_POLICY_STATUS_CHANGE:
    453                     onPolicyStatusChange();
    454                     break;
    455                 case MSG_FOCUS_GRANT:
    456                     if (mFocusListener != null) {
    457                         mFocusListener.onAudioFocusGrant(
    458                                 (AudioFocusInfo) msg.obj, msg.arg1);
    459                     }
    460                     break;
    461                 case MSG_FOCUS_LOSS:
    462                     if (mFocusListener != null) {
    463                         mFocusListener.onAudioFocusLoss(
    464                                 (AudioFocusInfo) msg.obj, msg.arg1 != 0);
    465                     }
    466                     break;
    467                 default:
    468                     Log.e(TAG, "Unknown event " + msg.what);
    469             }
    470         }
    471     }
    472 
    473     //==========================================================
    474     // Utils
    475     private static String addressForTag(AudioMix mix) {
    476         return "addr=" + mix.getRegistration();
    477     }
    478 
    479     private void sendMsg(int msg) {
    480         if (mEventHandler != null) {
    481             mEventHandler.sendEmptyMessage(msg);
    482         }
    483     }
    484 
    485     private void sendMsg(int msg, Object obj, int i) {
    486         if (mEventHandler != null) {
    487             mEventHandler.sendMessage(
    488                     mEventHandler.obtainMessage(msg, i /*arg1*/, 0 /*arg2, ignored*/, obj));
    489         }
    490     }
    491 
    492     private static IAudioService sService;
    493 
    494     private static IAudioService getService()
    495     {
    496         if (sService != null) {
    497             return sService;
    498         }
    499         IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
    500         sService = IAudioService.Stub.asInterface(b);
    501         return sService;
    502     }
    503 
    504     public String toLogFriendlyString() {
    505         String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
    506         textDump += "config=" + mConfig.toLogFriendlyString();
    507         return (textDump);
    508     }
    509 
    510     /** @hide */
    511     @IntDef({
    512         POLICY_STATUS_REGISTERED,
    513         POLICY_STATUS_UNREGISTERED
    514     })
    515     @Retention(RetentionPolicy.SOURCE)
    516     public @interface PolicyStatus {}
    517 }
    518