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