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