1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media.audiopolicy; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.SystemApi; 22 import android.content.Context; 23 import android.content.pm.PackageManager; 24 import android.media.AudioAttributes; 25 import android.media.AudioFocusInfo; 26 import android.media.AudioFormat; 27 import android.media.AudioManager; 28 import android.media.AudioRecord; 29 import android.media.AudioTrack; 30 import android.media.IAudioService; 31 import android.media.MediaRecorder; 32 import android.os.Binder; 33 import android.os.Handler; 34 import android.os.IBinder; 35 import android.os.Looper; 36 import android.os.Message; 37 import android.os.RemoteException; 38 import android.os.ServiceManager; 39 import android.util.Log; 40 import android.util.Slog; 41 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.RetentionPolicy; 44 import java.util.ArrayList; 45 46 /** 47 * @hide 48 * AudioPolicy provides access to the management of audio routing and audio focus. 49 */ 50 @SystemApi 51 public class AudioPolicy { 52 53 private static final String TAG = "AudioPolicy"; 54 private static final boolean DEBUG = false; 55 private final Object mLock = new Object(); 56 57 /** 58 * The status of an audio policy that is valid but cannot be used because it is not registered. 59 */ 60 @SystemApi 61 public static final int POLICY_STATUS_UNREGISTERED = 1; 62 /** 63 * The status of an audio policy that is valid, successfully registered and thus active. 64 */ 65 @SystemApi 66 public static final int POLICY_STATUS_REGISTERED = 2; 67 68 private int mStatus; 69 private String mRegistrationId; 70 private AudioPolicyStatusListener mStatusListener; 71 72 /** 73 * The behavior of a policy with regards to audio focus where it relies on the application 74 * to do the ducking, the is the legacy and default behavior. 75 */ 76 @SystemApi 77 public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; 78 public static final int FOCUS_POLICY_DUCKING_DEFAULT = FOCUS_POLICY_DUCKING_IN_APP; 79 /** 80 * The behavior of a policy with regards to audio focus where it handles ducking instead 81 * of the application losing focus and being signaled it can duck (as communicated by 82 * {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}). 83 * <br>Can only be used after having set a listener with 84 * {@link AudioPolicy#setAudioPolicyFocusListener(AudioPolicyFocusListener)}. 85 */ 86 @SystemApi 87 public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; 88 89 private AudioPolicyFocusListener mFocusListener; 90 91 private Context mContext; 92 93 private AudioPolicyConfig mConfig; 94 95 /** @hide */ getConfig()96 public AudioPolicyConfig getConfig() { return mConfig; } 97 /** @hide */ hasFocusListener()98 public boolean hasFocusListener() { return mFocusListener != null; } 99 100 /** 101 * The parameter is guaranteed non-null through the Builder 102 */ AudioPolicy(AudioPolicyConfig config, Context context, Looper looper, AudioPolicyFocusListener fl, AudioPolicyStatusListener sl)103 private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper, 104 AudioPolicyFocusListener fl, AudioPolicyStatusListener sl) { 105 mConfig = config; 106 mStatus = POLICY_STATUS_UNREGISTERED; 107 mContext = context; 108 if (looper == null) { 109 looper = Looper.getMainLooper(); 110 } 111 if (looper != null) { 112 mEventHandler = new EventHandler(this, looper); 113 } else { 114 mEventHandler = null; 115 Log.e(TAG, "No event handler due to looper without a thread"); 116 } 117 mFocusListener = fl; 118 mStatusListener = sl; 119 } 120 121 /** 122 * Builder class for {@link AudioPolicy} objects 123 */ 124 @SystemApi 125 public static class Builder { 126 private ArrayList<AudioMix> mMixes; 127 private Context mContext; 128 private Looper mLooper; 129 private AudioPolicyFocusListener mFocusListener; 130 private AudioPolicyStatusListener mStatusListener; 131 132 /** 133 * Constructs a new Builder with no audio mixes. 134 * @param context the context for the policy 135 */ 136 @SystemApi Builder(Context context)137 public Builder(Context context) { 138 mMixes = new ArrayList<AudioMix>(); 139 mContext = context; 140 } 141 142 /** 143 * Add an {@link AudioMix} to be part of the audio policy being built. 144 * @param mix a non-null {@link AudioMix} to be part of the audio policy. 145 * @return the same Builder instance. 146 * @throws IllegalArgumentException 147 */ 148 @SystemApi addMix(@onNull AudioMix mix)149 public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException { 150 if (mix == null) { 151 throw new IllegalArgumentException("Illegal null AudioMix argument"); 152 } 153 mMixes.add(mix); 154 return this; 155 } 156 157 /** 158 * Sets the {@link Looper} on which to run the event loop. 159 * @param looper a non-null specific Looper. 160 * @return the same Builder instance. 161 * @throws IllegalArgumentException 162 */ 163 @SystemApi setLooper(@onNull Looper looper)164 public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException { 165 if (looper == null) { 166 throw new IllegalArgumentException("Illegal null Looper argument"); 167 } 168 mLooper = looper; 169 return this; 170 } 171 172 /** 173 * Sets the audio focus listener for the policy. 174 * @param l a {@link AudioPolicy.AudioPolicyFocusListener} 175 */ 176 @SystemApi setAudioPolicyFocusListener(AudioPolicyFocusListener l)177 public void setAudioPolicyFocusListener(AudioPolicyFocusListener l) { 178 mFocusListener = l; 179 } 180 181 /** 182 * Sets the audio policy status listener. 183 * @param l a {@link AudioPolicy.AudioPolicyStatusListener} 184 */ 185 @SystemApi setAudioPolicyStatusListener(AudioPolicyStatusListener l)186 public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) { 187 mStatusListener = l; 188 } 189 190 @SystemApi build()191 public AudioPolicy build() { 192 if (mStatusListener != null) { 193 // the AudioPolicy status listener includes updates on each mix activity state 194 for (AudioMix mix : mMixes) { 195 mix.mCallbackFlags |= AudioMix.CALLBACK_FLAG_NOTIFY_ACTIVITY; 196 } 197 } 198 return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper, 199 mFocusListener, mStatusListener); 200 } 201 } 202 setRegistration(String regId)203 public void setRegistration(String regId) { 204 synchronized (mLock) { 205 mRegistrationId = regId; 206 mConfig.setRegistration(regId); 207 if (regId != null) { 208 mStatus = POLICY_STATUS_REGISTERED; 209 } else { 210 mStatus = POLICY_STATUS_UNREGISTERED; 211 } 212 } 213 sendMsg(MSG_POLICY_STATUS_CHANGE); 214 } 215 policyReadyToUse()216 private boolean policyReadyToUse() { 217 synchronized (mLock) { 218 if (mStatus != POLICY_STATUS_REGISTERED) { 219 Log.e(TAG, "Cannot use unregistered AudioPolicy"); 220 return false; 221 } 222 if (mContext == null) { 223 Log.e(TAG, "Cannot use AudioPolicy without context"); 224 return false; 225 } 226 if (mRegistrationId == null) { 227 Log.e(TAG, "Cannot use unregistered AudioPolicy"); 228 return false; 229 } 230 } 231 if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( 232 android.Manifest.permission.MODIFY_AUDIO_ROUTING))) { 233 Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid " 234 + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING"); 235 return false; 236 } 237 return true; 238 } 239 checkMixReadyToUse(AudioMix mix, boolean forTrack)240 private void checkMixReadyToUse(AudioMix mix, boolean forTrack) 241 throws IllegalArgumentException{ 242 if (mix == null) { 243 String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation" 244 : "Invalid null AudioMix for AudioRecord creation"; 245 throw new IllegalArgumentException(msg); 246 } 247 if (!mConfig.mMixes.contains(mix)) { 248 throw new IllegalArgumentException("Invalid mix: not part of this policy"); 249 } 250 if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK) 251 { 252 throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back"); 253 } 254 if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) { 255 throw new IllegalArgumentException( 256 "Invalid AudioMix: not defined for being a recording source"); 257 } 258 if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) { 259 throw new IllegalArgumentException( 260 "Invalid AudioMix: not defined for capturing playback"); 261 } 262 } 263 264 /** 265 * Returns the current behavior for audio focus-related ducking. 266 * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY} 267 */ 268 @SystemApi getFocusDuckingBehavior()269 public int getFocusDuckingBehavior() { 270 return mConfig.mDuckingPolicy; 271 } 272 273 // Note on implementation: not part of the Builder as there can be only one registered policy 274 // that handles ducking but there can be multiple policies 275 /** 276 * Sets the behavior for audio focus-related ducking. 277 * There must be a focus listener if this policy is to handle ducking. 278 * @param behavior {@link #FOCUS_POLICY_DUCKING_IN_APP} or 279 * {@link #FOCUS_POLICY_DUCKING_IN_POLICY} 280 * @return {@link AudioManager#SUCCESS} or {@link AudioManager#ERROR} (for instance if there 281 * is already an audio policy that handles ducking). 282 * @throws IllegalArgumentException 283 * @throws IllegalStateException 284 */ 285 @SystemApi setFocusDuckingBehavior(int behavior)286 public int setFocusDuckingBehavior(int behavior) 287 throws IllegalArgumentException, IllegalStateException { 288 if ((behavior != FOCUS_POLICY_DUCKING_IN_APP) 289 && (behavior != FOCUS_POLICY_DUCKING_IN_POLICY)) { 290 throw new IllegalArgumentException("Invalid ducking behavior " + behavior); 291 } 292 synchronized (mLock) { 293 if (mStatus != POLICY_STATUS_REGISTERED) { 294 throw new IllegalStateException( 295 "Cannot change ducking behavior for unregistered policy"); 296 } 297 if ((behavior == FOCUS_POLICY_DUCKING_IN_POLICY) 298 && (mFocusListener == null)) { 299 // there must be a focus listener if the policy handles ducking 300 throw new IllegalStateException( 301 "Cannot handle ducking without an audio focus listener"); 302 } 303 IAudioService service = getService(); 304 try { 305 final int status = service.setFocusPropertiesForPolicy(behavior /*duckingBehavior*/, 306 this.cb()); 307 if (status == AudioManager.SUCCESS) { 308 mConfig.mDuckingPolicy = behavior; 309 } 310 return status; 311 } catch (RemoteException e) { 312 Log.e(TAG, "Dead object in setFocusPropertiesForPolicy for behavior", e); 313 return AudioManager.ERROR; 314 } 315 } 316 } 317 318 /** 319 * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}. 320 * Audio buffers recorded through the created instance will contain the mix of the audio 321 * streams that fed the given mixer. 322 * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with 323 * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy. 324 * @return a new {@link AudioRecord} instance whose data format is the one defined in the 325 * {@link AudioMix}, or null if this policy was not successfully registered 326 * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. 327 * @throws IllegalArgumentException 328 */ 329 @SystemApi createAudioRecordSink(AudioMix mix)330 public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException { 331 if (!policyReadyToUse()) { 332 Log.e(TAG, "Cannot create AudioRecord sink for AudioMix"); 333 return null; 334 } 335 checkMixReadyToUse(mix, false/*not for an AudioTrack*/); 336 // create an AudioFormat from the mix format compatible with recording, as the mix 337 // was defined for playback 338 AudioFormat mixFormat = new AudioFormat.Builder(mix.getFormat()) 339 .setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask( 340 mix.getFormat().getChannelMask())) 341 .build(); 342 // create the AudioRecord, configured for loop back, using the same format as the mix 343 AudioRecord ar = new AudioRecord( 344 new AudioAttributes.Builder() 345 .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX) 346 .addTag(addressForTag(mix)) 347 .build(), 348 mixFormat, 349 AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(), 350 // using stereo for buffer size to avoid the current poor support for masks 351 AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()), 352 AudioManager.AUDIO_SESSION_ID_GENERATE 353 ); 354 return ar; 355 } 356 357 /** 358 * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}. 359 * Audio buffers played through the created instance will be sent to the given mix 360 * to be recorded through the recording APIs. 361 * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with 362 * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy. 363 * @return a new {@link AudioTrack} instance whose data format is the one defined in the 364 * {@link AudioMix}, or null if this policy was not successfully registered 365 * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. 366 * @throws IllegalArgumentException 367 */ 368 @SystemApi createAudioTrackSource(AudioMix mix)369 public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException { 370 if (!policyReadyToUse()) { 371 Log.e(TAG, "Cannot create AudioTrack source for AudioMix"); 372 return null; 373 } 374 checkMixReadyToUse(mix, true/*for an AudioTrack*/); 375 // create the AudioTrack, configured for loop back, using the same format as the mix 376 AudioTrack at = new AudioTrack( 377 new AudioAttributes.Builder() 378 .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE) 379 .addTag(addressForTag(mix)) 380 .build(), 381 mix.getFormat(), 382 AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(), 383 mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()), 384 AudioTrack.MODE_STREAM, 385 AudioManager.AUDIO_SESSION_ID_GENERATE 386 ); 387 return at; 388 } 389 390 @SystemApi getStatus()391 public int getStatus() { 392 return mStatus; 393 } 394 395 @SystemApi 396 public static abstract class AudioPolicyStatusListener { onStatusChange()397 public void onStatusChange() {} onMixStateUpdate(AudioMix mix)398 public void onMixStateUpdate(AudioMix mix) {} 399 } 400 401 @SystemApi 402 public static abstract class AudioPolicyFocusListener { onAudioFocusGrant(AudioFocusInfo afi, int requestResult)403 public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {} onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified)404 public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {} 405 } 406 onPolicyStatusChange()407 private void onPolicyStatusChange() { 408 AudioPolicyStatusListener l; 409 synchronized (mLock) { 410 if (mStatusListener == null) { 411 return; 412 } 413 l = mStatusListener; 414 } 415 l.onStatusChange(); 416 } 417 418 //================================================== 419 // Callback interface 420 421 /** @hide */ cb()422 public IAudioPolicyCallback cb() { return mPolicyCb; } 423 424 private final IAudioPolicyCallback mPolicyCb = new IAudioPolicyCallback.Stub() { 425 426 public void notifyAudioFocusGrant(AudioFocusInfo afi, int requestResult) { 427 sendMsg(MSG_FOCUS_GRANT, afi, requestResult); 428 if (DEBUG) { 429 Log.v(TAG, "notifyAudioFocusGrant: pack=" + afi.getPackageName() + " client=" 430 + afi.getClientId() + "reqRes=" + requestResult); 431 } 432 } 433 434 public void notifyAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) { 435 sendMsg(MSG_FOCUS_LOSS, afi, wasNotified ? 1 : 0); 436 if (DEBUG) { 437 Log.v(TAG, "notifyAudioFocusLoss: pack=" + afi.getPackageName() + " client=" 438 + afi.getClientId() + "wasNotified=" + wasNotified); 439 } 440 } 441 442 public void notifyMixStateUpdate(String regId, int state) { 443 for (AudioMix mix : mConfig.getMixes()) { 444 if (mix.getRegistration().equals(regId)) { 445 mix.mMixState = state; 446 sendMsg(MSG_MIX_STATE_UPDATE, mix, 0/*ignored*/); 447 if (DEBUG) { 448 Log.v(TAG, "notifyMixStateUpdate: regId=" + regId + " state=" + state); 449 } 450 } 451 } 452 } 453 }; 454 455 //================================================== 456 // Event handling 457 private final EventHandler mEventHandler; 458 private final static int MSG_POLICY_STATUS_CHANGE = 0; 459 private final static int MSG_FOCUS_GRANT = 1; 460 private final static int MSG_FOCUS_LOSS = 2; 461 private final static int MSG_MIX_STATE_UPDATE = 3; 462 463 private class EventHandler extends Handler { EventHandler(AudioPolicy ap, Looper looper)464 public EventHandler(AudioPolicy ap, Looper looper) { 465 super(looper); 466 } 467 468 @Override handleMessage(Message msg)469 public void handleMessage(Message msg) { 470 switch(msg.what) { 471 case MSG_POLICY_STATUS_CHANGE: 472 onPolicyStatusChange(); 473 break; 474 case MSG_FOCUS_GRANT: 475 if (mFocusListener != null) { 476 mFocusListener.onAudioFocusGrant( 477 (AudioFocusInfo) msg.obj, msg.arg1); 478 } 479 break; 480 case MSG_FOCUS_LOSS: 481 if (mFocusListener != null) { 482 mFocusListener.onAudioFocusLoss( 483 (AudioFocusInfo) msg.obj, msg.arg1 != 0); 484 } 485 break; 486 case MSG_MIX_STATE_UPDATE: 487 if (mStatusListener != null) { 488 mStatusListener.onMixStateUpdate((AudioMix) msg.obj); 489 } 490 break; 491 default: 492 Log.e(TAG, "Unknown event " + msg.what); 493 } 494 } 495 } 496 497 //========================================================== 498 // Utils addressForTag(AudioMix mix)499 private static String addressForTag(AudioMix mix) { 500 return "addr=" + mix.getRegistration(); 501 } 502 sendMsg(int msg)503 private void sendMsg(int msg) { 504 if (mEventHandler != null) { 505 mEventHandler.sendEmptyMessage(msg); 506 } 507 } 508 sendMsg(int msg, Object obj, int i)509 private void sendMsg(int msg, Object obj, int i) { 510 if (mEventHandler != null) { 511 mEventHandler.sendMessage( 512 mEventHandler.obtainMessage(msg, i /*arg1*/, 0 /*arg2, ignored*/, obj)); 513 } 514 } 515 516 private static IAudioService sService; 517 getService()518 private static IAudioService getService() 519 { 520 if (sService != null) { 521 return sService; 522 } 523 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); 524 sService = IAudioService.Stub.asInterface(b); 525 return sService; 526 } 527 toLogFriendlyString()528 public String toLogFriendlyString() { 529 String textDump = new String("android.media.audiopolicy.AudioPolicy:\n"); 530 textDump += "config=" + mConfig.toLogFriendlyString(); 531 return (textDump); 532 } 533 534 /** @hide */ 535 @IntDef({ 536 POLICY_STATUS_REGISTERED, 537 POLICY_STATUS_UNREGISTERED 538 }) 539 @Retention(RetentionPolicy.SOURCE) 540 public @interface PolicyStatus {} 541 } 542