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 static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION; 20 21 import android.annotation.FlaggedApi; 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.RequiresPermission; 26 import android.annotation.SystemApi; 27 import android.annotation.TestApi; 28 import android.annotation.UserIdInt; 29 import android.app.ActivityManager; 30 import android.content.AttributionSource; 31 import android.content.Context; 32 import android.content.pm.PackageManager; 33 import android.media.AudioAttributes; 34 import android.media.AudioDeviceInfo; 35 import android.media.AudioFocusInfo; 36 import android.media.AudioFormat; 37 import android.media.AudioManager; 38 import android.media.AudioRecord; 39 import android.media.AudioTrack; 40 import android.media.FadeManagerConfiguration; 41 import android.media.IAudioService; 42 import android.media.MediaRecorder; 43 import android.media.projection.MediaProjection; 44 import android.os.Binder; 45 import android.os.Handler; 46 import android.os.IBinder; 47 import android.os.Looper; 48 import android.os.Message; 49 import android.os.RemoteException; 50 import android.os.ServiceManager; 51 import android.util.Log; 52 import android.util.Pair; 53 import android.util.Slog; 54 55 import com.android.internal.annotations.GuardedBy; 56 import com.android.internal.util.Preconditions; 57 58 import java.lang.annotation.Retention; 59 import java.lang.annotation.RetentionPolicy; 60 import java.lang.ref.WeakReference; 61 import java.util.ArrayList; 62 import java.util.Collections; 63 import java.util.List; 64 import java.util.Objects; 65 66 /** 67 * @hide 68 * AudioPolicy provides access to the management of audio routing and audio focus. 69 */ 70 @SystemApi 71 public class AudioPolicy { 72 73 private static final String TAG = "AudioPolicy"; 74 private static final boolean DEBUG = false; 75 private final Object mLock = new Object(); 76 77 /** 78 * The status of an audio policy that is valid but cannot be used because it is not registered. 79 */ 80 public static final int POLICY_STATUS_UNREGISTERED = 1; 81 /** 82 * The status of an audio policy that is valid, successfully registered and thus active. 83 */ 84 public static final int POLICY_STATUS_REGISTERED = 2; 85 86 @GuardedBy("mLock") 87 private int mStatus; 88 @GuardedBy("mLock") 89 private String mRegistrationId; 90 private final AudioPolicyStatusListener mStatusListener; 91 private final boolean mIsFocusPolicy; 92 private final boolean mIsTestFocusPolicy; 93 94 /** 95 * The list of AudioTrack instances created to inject audio into the associated mixes 96 * Lazy initialization in {@link #createAudioTrackSource(AudioMix)} 97 */ 98 @GuardedBy("mLock") 99 @Nullable private ArrayList<WeakReference<AudioTrack>> mInjectors; 100 /** 101 * The list AudioRecord instances created to capture audio from the associated mixes 102 * Lazy initialization in {@link #createAudioRecordSink(AudioMix)} 103 */ 104 @GuardedBy("mLock") 105 @Nullable private ArrayList<WeakReference<AudioRecord>> mCaptors; 106 107 /** 108 * The behavior of a policy with regards to audio focus where it relies on the application 109 * to do the ducking, the is the legacy and default behavior. 110 */ 111 public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; 112 public static final int FOCUS_POLICY_DUCKING_DEFAULT = FOCUS_POLICY_DUCKING_IN_APP; 113 /** 114 * The behavior of a policy with regards to audio focus where it handles ducking instead 115 * of the application losing focus and being signaled it can duck (as communicated by 116 * {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}). 117 * <br>Can only be used after having set a listener with 118 * {@link AudioPolicy#setAudioPolicyFocusListener(AudioPolicyFocusListener)}. 119 */ 120 public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; 121 122 private AudioPolicyFocusListener mFocusListener; 123 124 private final AudioPolicyVolumeCallback mVolCb; 125 126 private Context mContext; 127 128 @GuardedBy("mLock") 129 private AudioPolicyConfig mConfig; 130 131 private final MediaProjection mProjection; 132 133 /** @hide */ getConfig()134 public AudioPolicyConfig getConfig() { return mConfig; } 135 /** @hide */ hasFocusListener()136 public boolean hasFocusListener() { return mFocusListener != null; } 137 /** @hide */ isFocusPolicy()138 public boolean isFocusPolicy() { return mIsFocusPolicy; } 139 /** @hide */ isTestFocusPolicy()140 public boolean isTestFocusPolicy() { 141 return mIsTestFocusPolicy; 142 } 143 /** @hide */ isVolumeController()144 public boolean isVolumeController() { return mVolCb != null; } 145 /** @hide */ getMediaProjection()146 public @Nullable MediaProjection getMediaProjection() { 147 return mProjection; 148 } 149 150 /** @hide */ getAttributionSource()151 public AttributionSource getAttributionSource() { 152 return getAttributionSource(mContext); 153 } 154 getAttributionSource(Context context)155 private static AttributionSource getAttributionSource(Context context) { 156 return context == null 157 ? AttributionSource.myAttributionSource() : context.getAttributionSource(); 158 } 159 160 /** 161 * The parameters are guaranteed non-null through the Builder 162 */ AudioPolicy(AudioPolicyConfig config, Context context, Looper looper, AudioPolicyFocusListener fl, AudioPolicyStatusListener sl, boolean isFocusPolicy, boolean isTestFocusPolicy, AudioPolicyVolumeCallback vc, @Nullable MediaProjection projection)163 private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper, 164 AudioPolicyFocusListener fl, AudioPolicyStatusListener sl, 165 boolean isFocusPolicy, boolean isTestFocusPolicy, 166 AudioPolicyVolumeCallback vc, @Nullable MediaProjection projection) { 167 mConfig = config; 168 mStatus = POLICY_STATUS_UNREGISTERED; 169 mContext = context; 170 if (looper == null) { 171 looper = Looper.getMainLooper(); 172 } 173 if (looper != null) { 174 mEventHandler = new EventHandler(this, looper); 175 } else { 176 mEventHandler = null; 177 Log.e(TAG, "No event handler due to looper without a thread"); 178 } 179 mFocusListener = fl; 180 mStatusListener = sl; 181 mIsFocusPolicy = isFocusPolicy; 182 mIsTestFocusPolicy = isTestFocusPolicy; 183 mVolCb = vc; 184 mProjection = projection; 185 } 186 187 /** 188 * Builder class for {@link AudioPolicy} objects. 189 * By default the policy to be created doesn't govern audio focus decisions. 190 */ 191 public static class Builder { 192 private ArrayList<AudioMix> mMixes; 193 private Context mContext; 194 private Looper mLooper; 195 private AudioPolicyFocusListener mFocusListener; 196 private AudioPolicyStatusListener mStatusListener; 197 private boolean mIsFocusPolicy = false; 198 private boolean mIsTestFocusPolicy = false; 199 private AudioPolicyVolumeCallback mVolCb; 200 private MediaProjection mProjection; 201 202 /** 203 * Constructs a new Builder with no audio mixes. 204 * @param context the context for the policy 205 */ Builder(Context context)206 public Builder(Context context) { 207 mMixes = new ArrayList<AudioMix>(); 208 mContext = context; 209 } 210 211 /** 212 * Add an {@link AudioMix} to be part of the audio policy being built. 213 * @param mix a non-null {@link AudioMix} to be part of the audio policy. 214 * @return the same Builder instance. 215 * @throws IllegalArgumentException 216 */ 217 @NonNull addMix(@onNull AudioMix mix)218 public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException { 219 if (mix == null) { 220 throw new IllegalArgumentException("Illegal null AudioMix argument"); 221 } 222 if (android.permission.flags.Flags.deviceAwarePermissionApisEnabled()) { 223 mix.setVirtualDeviceId(getAttributionSource(mContext).getDeviceId()); 224 } 225 mMixes.add(mix); 226 return this; 227 } 228 229 /** 230 * Sets the {@link Looper} on which to run the event loop. 231 * @param looper a non-null specific Looper. 232 * @return the same Builder instance. 233 * @throws IllegalArgumentException 234 */ 235 @NonNull setLooper(@onNull Looper looper)236 public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException { 237 if (looper == null) { 238 throw new IllegalArgumentException("Illegal null Looper argument"); 239 } 240 mLooper = looper; 241 return this; 242 } 243 244 /** 245 * Sets the audio focus listener for the policy. 246 * @param l a {@link AudioPolicy.AudioPolicyFocusListener} 247 */ setAudioPolicyFocusListener(AudioPolicyFocusListener l)248 public void setAudioPolicyFocusListener(AudioPolicyFocusListener l) { 249 mFocusListener = l; 250 } 251 252 /** 253 * Declares whether this policy will grant and deny audio focus through 254 * the {@link AudioPolicy.AudioPolicyFocusListener}. 255 * If set to {@code true}, it is mandatory to set an 256 * {@link AudioPolicy.AudioPolicyFocusListener} in order to successfully build 257 * an {@code AudioPolicy} instance. 258 * @param isFocusPolicy true if the policy will govern audio focus decisions. 259 * @return the same Builder instance. 260 */ 261 @NonNull setIsAudioFocusPolicy(boolean isFocusPolicy)262 public Builder setIsAudioFocusPolicy(boolean isFocusPolicy) { 263 mIsFocusPolicy = isFocusPolicy; 264 return this; 265 } 266 267 /** 268 * @hide 269 * Test method to declare whether this audio focus policy is for test purposes only. 270 * Having a test policy registered will disable the current focus policy and replace it 271 * with this test policy. When unregistered, the previous focus policy will be restored. 272 * <p>A value of <code>true</code> will be ignored if the AudioPolicy is not also 273 * focus policy. 274 * @param isTestFocusPolicy true if the focus policy to register is for testing purposes. 275 * @return the same Builder instance 276 */ 277 @TestApi 278 @NonNull setIsTestFocusPolicy(boolean isTestFocusPolicy)279 public Builder setIsTestFocusPolicy(boolean isTestFocusPolicy) { 280 mIsTestFocusPolicy = isTestFocusPolicy; 281 return this; 282 } 283 284 /** 285 * Sets the audio policy status listener. 286 * @param l a {@link AudioPolicy.AudioPolicyStatusListener} 287 */ setAudioPolicyStatusListener(AudioPolicyStatusListener l)288 public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) { 289 mStatusListener = l; 290 } 291 292 /** 293 * Sets the callback to receive all volume key-related events. 294 * The callback will only be called if the device is configured to handle volume events 295 * in the PhoneWindowManager (see config_handleVolumeKeysInWindowManager) 296 * @param vc 297 * @return the same Builder instance. 298 */ 299 @NonNull setAudioPolicyVolumeCallback(@onNull AudioPolicyVolumeCallback vc)300 public Builder setAudioPolicyVolumeCallback(@NonNull AudioPolicyVolumeCallback vc) { 301 if (vc == null) { 302 throw new IllegalArgumentException("Invalid null volume callback"); 303 } 304 mVolCb = vc; 305 return this; 306 } 307 308 /** 309 * Set a media projection obtained through createMediaProjection(). 310 * 311 * A MediaProjection that can project audio allows to register an audio 312 * policy LOOPBACK|RENDER without the MODIFY_AUDIO_ROUTING permission. 313 * 314 * @hide 315 */ 316 @NonNull setMediaProjection(@onNull MediaProjection projection)317 public Builder setMediaProjection(@NonNull MediaProjection projection) { 318 if (projection == null) { 319 throw new IllegalArgumentException("Invalid null volume callback"); 320 } 321 mProjection = projection; 322 return this; 323 324 } 325 326 /** 327 * Combines all of the attributes that have been set on this {@code Builder} and returns a 328 * new {@link AudioPolicy} object. 329 * @return a new {@code AudioPolicy} object. 330 * @throws IllegalStateException if there is no 331 * {@link AudioPolicy.AudioPolicyStatusListener} but the policy was configured 332 * as an audio focus policy with {@link #setIsAudioFocusPolicy(boolean)}. 333 */ 334 @NonNull build()335 public AudioPolicy build() { 336 if (mStatusListener != null) { 337 // the AudioPolicy status listener includes updates on each mix activity state 338 for (AudioMix mix : mMixes) { 339 mix.mCallbackFlags |= AudioMix.CALLBACK_FLAG_NOTIFY_ACTIVITY; 340 } 341 } 342 if (mIsFocusPolicy && mFocusListener == null) { 343 throw new IllegalStateException("Cannot be a focus policy without " 344 + "an AudioPolicyFocusListener"); 345 } 346 return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper, 347 mFocusListener, mStatusListener, mIsFocusPolicy, mIsTestFocusPolicy, 348 mVolCb, mProjection); 349 } 350 } 351 352 /** 353 * Update the current configuration of the set of audio mixes by adding new ones, while 354 * keeping the policy registered. If any of the provided audio mixes is invalid then none of 355 * the passed mixes will be registered. 356 * 357 * This method can only be called on a registered policy. 358 * @param mixes the list of {@link AudioMix} to add 359 * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR} 360 * otherwise. 361 */ attachMixes(@onNull List<AudioMix> mixes)362 public int attachMixes(@NonNull List<AudioMix> mixes) { 363 if (mixes == null) { 364 throw new IllegalArgumentException("Illegal null list of AudioMix"); 365 } 366 synchronized (mLock) { 367 if (mStatus != POLICY_STATUS_REGISTERED) { 368 throw new IllegalStateException("Cannot alter unregistered AudioPolicy"); 369 } 370 final ArrayList<AudioMix> zeMixes = new ArrayList<AudioMix>(mixes.size()); 371 for (AudioMix mix : mixes) { 372 if (mix == null) { 373 throw new IllegalArgumentException("Illegal null AudioMix in attachMixes"); 374 } else { 375 if (android.permission.flags.Flags.deviceAwarePermissionApisEnabled()) { 376 mix.setVirtualDeviceId(getAttributionSource(mContext).getDeviceId()); 377 } 378 zeMixes.add(mix); 379 } 380 } 381 final AudioPolicyConfig cfg = new AudioPolicyConfig(zeMixes); 382 IAudioService service = getService(); 383 try { 384 final int status = service.addMixForPolicy(cfg, this.cb()); 385 if (status == AudioManager.SUCCESS) { 386 mConfig.add(zeMixes); 387 } 388 return status; 389 } catch (RemoteException e) { 390 Log.e(TAG, "Dead object in attachMixes", e); 391 return AudioManager.ERROR; 392 } 393 } 394 } 395 396 /** 397 * Update the current configuration of the set of audio mixes for this audio policy by 398 * removing some, while keeping the policy registered. Will unregister all provided audio 399 * mixes, if possible. 400 * 401 * This method can only be called on a registered policy and only affects this current policy. 402 * @param mixes the list of {@link AudioMix} to remove 403 * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR} 404 * otherwise. If only some of the provided audio mixes were detached but any one mix 405 * failed to be detached, this method returns {@link AudioManager#ERROR}. 406 */ detachMixes(@onNull List<AudioMix> mixes)407 public int detachMixes(@NonNull List<AudioMix> mixes) { 408 if (mixes == null) { 409 throw new IllegalArgumentException("Illegal null list of AudioMix"); 410 } 411 synchronized (mLock) { 412 if (mStatus != POLICY_STATUS_REGISTERED) { 413 throw new IllegalStateException("Cannot alter unregistered AudioPolicy"); 414 } 415 final ArrayList<AudioMix> zeMixes = new ArrayList<AudioMix>(mixes.size()); 416 for (AudioMix mix : mixes) { 417 if (mix == null) { 418 throw new IllegalArgumentException("Illegal null AudioMix in detachMixes"); 419 } else { 420 if (android.permission.flags.Flags.deviceAwarePermissionApisEnabled()) { 421 mix.setVirtualDeviceId(getAttributionSource(mContext).getDeviceId()); 422 } 423 zeMixes.add(mix); 424 } 425 } 426 final AudioPolicyConfig cfg = new AudioPolicyConfig(zeMixes); 427 IAudioService service = getService(); 428 try { 429 final int status = service.removeMixForPolicy(cfg, this.cb()); 430 if (status == AudioManager.SUCCESS) { 431 mConfig.remove(zeMixes); 432 } 433 return status; 434 } catch (RemoteException e) { 435 Log.e(TAG, "Dead object in detachMixes", e); 436 return AudioManager.ERROR; 437 } 438 } 439 } 440 441 /** 442 * Update {@link AudioMixingRule}-s of already registered {@link AudioMix}-es. 443 * 444 * @param mixingRuleUpdates - {@link List} of {@link Pair}-s, each pair containing 445 * {@link AudioMix} to update and its new corresponding {@link AudioMixingRule}. 446 * 447 * @return {@link AudioManager#SUCCESS} if the update was successful, 448 * {@link AudioManager#ERROR} otherwise. 449 */ 450 @FlaggedApi(Flags.FLAG_AUDIO_POLICY_UPDATE_MIXING_RULES_API) 451 @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) updateMixingRules( @onNull List<Pair<AudioMix, AudioMixingRule>> mixingRuleUpdates)452 public int updateMixingRules( 453 @NonNull List<Pair<AudioMix, AudioMixingRule>> mixingRuleUpdates) { 454 Objects.requireNonNull(mixingRuleUpdates); 455 456 IAudioService service = getService(); 457 try { 458 synchronized (mLock) { 459 final int status = service.updateMixingRulesForPolicy( 460 mixingRuleUpdates.stream().map(p -> p.first).toArray(AudioMix[]::new), 461 mixingRuleUpdates.stream().map(p -> p.second).toArray( 462 AudioMixingRule[]::new), 463 cb()); 464 if (status == AudioManager.SUCCESS) { 465 mConfig.updateMixingRules(mixingRuleUpdates); 466 } 467 return status; 468 } 469 } catch (RemoteException e) { 470 Log.e(TAG, "Received remote exeception in updateMixingRules call: ", e); 471 return AudioManager.ERROR; 472 } 473 } 474 475 /** 476 * @hide 477 * Configures the audio framework so that all audio streams originating from the given UID 478 * can only come from a set of audio devices. 479 * For this routing to be operational, a number of {@link AudioMix} instances must have been 480 * previously registered on this policy, and routed to a super-set of the given audio devices 481 * with {@link AudioMix.Builder#setDevice(android.media.AudioDeviceInfo)}. Note that having 482 * multiple devices in the list doesn't imply the signals will be duplicated on the different 483 * audio devices, final routing will depend on the {@link AudioAttributes} of the sounds being 484 * played. 485 * @param uid UID of the application to affect. 486 * @param devices list of devices to which the audio stream of the application may be routed. 487 * @return true if the change was successful, false otherwise. 488 */ 489 @SystemApi setUidDeviceAffinity(int uid, @NonNull List<AudioDeviceInfo> devices)490 public boolean setUidDeviceAffinity(int uid, @NonNull List<AudioDeviceInfo> devices) { 491 if (devices == null) { 492 throw new IllegalArgumentException("Illegal null list of audio devices"); 493 } 494 synchronized (mLock) { 495 if (mStatus != POLICY_STATUS_REGISTERED) { 496 throw new IllegalStateException("Cannot use unregistered AudioPolicy"); 497 } 498 final int[] deviceTypes = new int[devices.size()]; 499 final String[] deviceAdresses = new String[devices.size()]; 500 int i = 0; 501 for (AudioDeviceInfo device : devices) { 502 if (device == null) { 503 throw new IllegalArgumentException( 504 "Illegal null AudioDeviceInfo in setUidDeviceAffinity"); 505 } 506 deviceTypes[i] = 507 AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()); 508 deviceAdresses[i] = device.getAddress(); 509 i++; 510 } 511 final IAudioService service = getService(); 512 try { 513 final int status = service.setUidDeviceAffinity(this.cb(), 514 uid, deviceTypes, deviceAdresses); 515 return (status == AudioManager.SUCCESS); 516 } catch (RemoteException e) { 517 Log.e(TAG, "Dead object in setUidDeviceAffinity", e); 518 return false; 519 } 520 } 521 } 522 523 /** 524 * @hide 525 * Removes audio device affinity previously set by 526 * {@link #setUidDeviceAffinity(int, java.util.List)}. 527 * @param uid UID of the application affected. 528 * @return true if the change was successful, false otherwise. 529 */ 530 @SystemApi removeUidDeviceAffinity(int uid)531 public boolean removeUidDeviceAffinity(int uid) { 532 synchronized (mLock) { 533 if (mStatus != POLICY_STATUS_REGISTERED) { 534 throw new IllegalStateException("Cannot use unregistered AudioPolicy"); 535 } 536 final IAudioService service = getService(); 537 try { 538 final int status = service.removeUidDeviceAffinity(this.cb(), uid); 539 return (status == AudioManager.SUCCESS); 540 } catch (RemoteException e) { 541 Log.e(TAG, "Dead object in removeUidDeviceAffinity", e); 542 return false; 543 } 544 } 545 } 546 547 /** 548 * @hide 549 * Removes audio device affinity previously set by 550 * {@link #setUserIdDeviceAffinity(int, java.util.List)}. 551 * @param userId userId of the application affected, as obtained via 552 * {@link UserHandle#getIdentifier}. Not to be confused with application uid. 553 * @return true if the change was successful, false otherwise. 554 */ 555 @SystemApi removeUserIdDeviceAffinity(@serIdInt int userId)556 public boolean removeUserIdDeviceAffinity(@UserIdInt int userId) { 557 synchronized (mLock) { 558 if (mStatus != POLICY_STATUS_REGISTERED) { 559 throw new IllegalStateException("Cannot use unregistered AudioPolicy"); 560 } 561 final IAudioService service = getService(); 562 try { 563 final int status = service.removeUserIdDeviceAffinity(this.cb(), userId); 564 return (status == AudioManager.SUCCESS); 565 } catch (RemoteException e) { 566 Log.e(TAG, "Dead object in removeUserIdDeviceAffinity", e); 567 return false; 568 } 569 } 570 } 571 572 /** 573 * @hide 574 * Configures the audio framework so that all audio streams originating from the given user 575 * can only come from a set of audio devices. 576 * For this routing to be operational, a number of {@link AudioMix} instances must have been 577 * previously registered on this policy, and routed to a super-set of the given audio devices 578 * with {@link AudioMix.Builder#setDevice(android.media.AudioDeviceInfo)}. Note that having 579 * multiple devices in the list doesn't imply the signals will be duplicated on the different 580 * audio devices, final routing will depend on the {@link AudioAttributes} of the sounds being 581 * played. 582 * @param userId userId of the application affected, as obtained via 583 * {@link UserHandle#getIdentifier}. Not to be confused with application uid. 584 * @param devices list of devices to which the audio stream of the application may be routed. 585 * @return true if the change was successful, false otherwise. 586 */ 587 @SystemApi setUserIdDeviceAffinity(@serIdInt int userId, @NonNull List<AudioDeviceInfo> devices)588 public boolean setUserIdDeviceAffinity(@UserIdInt int userId, 589 @NonNull List<AudioDeviceInfo> devices) { 590 Objects.requireNonNull(devices, "Illegal null list of audio devices"); 591 synchronized (mLock) { 592 if (mStatus != POLICY_STATUS_REGISTERED) { 593 throw new IllegalStateException("Cannot use unregistered AudioPolicy"); 594 } 595 final int[] deviceTypes = new int[devices.size()]; 596 final String[] deviceAddresses = new String[devices.size()]; 597 int i = 0; 598 for (AudioDeviceInfo device : devices) { 599 if (device == null) { 600 throw new IllegalArgumentException( 601 "Illegal null AudioDeviceInfo in setUserIdDeviceAffinity"); 602 } 603 deviceTypes[i] = 604 AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()); 605 deviceAddresses[i] = device.getAddress(); 606 i++; 607 } 608 final IAudioService service = getService(); 609 try { 610 final int status = service.setUserIdDeviceAffinity(this.cb(), 611 userId, deviceTypes, deviceAddresses); 612 return (status == AudioManager.SUCCESS); 613 } catch (RemoteException e) { 614 Log.e(TAG, "Dead object in setUserIdDeviceAffinity", e); 615 return false; 616 } 617 } 618 } 619 620 /** @hide */ reset()621 public void reset() { 622 setRegistration(null); 623 } 624 625 /** 626 * @hide 627 */ 628 @TestApi 629 @NonNull 630 @FlaggedApi(Flags.FLAG_AUDIO_MIX_TEST_API) getMixes()631 public List<AudioMix> getMixes() { 632 if (!Flags.audioMixTestApi()) { 633 return Collections.emptyList(); 634 } 635 synchronized (mLock) { 636 return List.copyOf(mConfig.getMixes()); 637 } 638 } 639 setRegistration(String regId)640 public void setRegistration(String regId) { 641 synchronized (mLock) { 642 mRegistrationId = regId; 643 mConfig.setRegistration(regId); 644 if (regId != null) { 645 mStatus = POLICY_STATUS_REGISTERED; 646 } else { 647 mStatus = POLICY_STATUS_UNREGISTERED; 648 mConfig.reset(); 649 } 650 } 651 sendMsg(MSG_POLICY_STATUS_CHANGE); 652 } 653 654 /**@hide*/ getRegistration()655 public String getRegistration() { 656 return mRegistrationId; 657 } 658 659 /** 660 * Sets a custom {@link FadeManagerConfiguration} to handle fade cycle of players during 661 * {@link android.media.AudioManager#AUDIOFOCUS_LOSS} 662 * 663 * @param fmcForFocusLoss custom {@link FadeManagerConfiguration} 664 * @return {@link AudioManager#SUCCESS} if the update was successful, 665 * {@link AudioManager#ERROR} otherwise 666 * @throws IllegalStateException if the audio policy is not registered 667 * @hide 668 */ 669 @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION) 670 @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) 671 @SystemApi setFadeManagerConfigurationForFocusLoss( @onNull FadeManagerConfiguration fmcForFocusLoss)672 public int setFadeManagerConfigurationForFocusLoss( 673 @NonNull FadeManagerConfiguration fmcForFocusLoss) { 674 Objects.requireNonNull(fmcForFocusLoss, 675 "FadeManagerConfiguration for focus loss cannot be null"); 676 677 IAudioService service = getService(); 678 synchronized (mLock) { 679 Preconditions.checkState(isAudioPolicyRegisteredLocked(), 680 "Cannot set FadeManagerConfiguration with unregistered AudioPolicy"); 681 682 try { 683 return service.setFadeManagerConfigurationForFocusLoss(fmcForFocusLoss); 684 } catch (RemoteException e) { 685 Log.e(TAG, "Received remote exception for setFadeManagerConfigurationForFocusLoss:", 686 e); 687 throw e.rethrowFromSystemServer(); 688 } 689 } 690 } 691 692 /** 693 * Clear the current {@link FadeManagerConfiguration} set to handle fade cycles of players 694 * during {@link android.media.AudioManager#AUDIOFOCUS_LOSS} 695 * 696 * <p>In the absence of custom {@link FadeManagerConfiguration}, the default configurations will 697 * be used to handle fade cycles during audio focus loss. 698 * 699 * @return {@link AudioManager#SUCCESS} if the update was successful, 700 * {@link AudioManager#ERROR} otherwise 701 * @throws IllegalStateException if the audio policy is not registered 702 * @see #setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration) 703 * @hide 704 */ 705 @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION) 706 @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) 707 @SystemApi clearFadeManagerConfigurationForFocusLoss()708 public int clearFadeManagerConfigurationForFocusLoss() { 709 IAudioService service = getService(); 710 synchronized (mLock) { 711 Preconditions.checkState(isAudioPolicyRegisteredLocked(), 712 "Cannot clear FadeManagerConfiguration from unregistered AudioPolicy"); 713 714 try { 715 return service.clearFadeManagerConfigurationForFocusLoss(); 716 } catch (RemoteException e) { 717 Log.e(TAG, "Received remote exception for " 718 + "clearFadeManagerConfigurationForFocusLoss:", e); 719 throw e.rethrowFromSystemServer(); 720 } 721 } 722 } 723 724 /** 725 * Get the current fade manager configuration used for fade operations during 726 * {@link android.media.AudioManager#AUDIOFOCUS_LOSS} 727 * 728 * <p>If no custom {@link FadeManagerConfiguration} is set, the default configuration currently 729 * active will be returned. 730 * 731 * @return the active {@link FadeManagerConfiguration} used during audio focus loss 732 * @throws IllegalStateException if the audio policy is not registered 733 * @see #setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration) 734 * @see #clearFadeManagerConfigurationForFocusLoss() 735 * @hide 736 */ 737 @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION) 738 @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) 739 @SystemApi 740 @NonNull getFadeManagerConfigurationForFocusLoss()741 public FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss() { 742 IAudioService service = getService(); 743 synchronized (mLock) { 744 Preconditions.checkState(isAudioPolicyRegisteredLocked(), 745 "Cannot get FadeManagerConfiguration from unregistered AudioPolicy"); 746 747 try { 748 return service.getFadeManagerConfigurationForFocusLoss(); 749 } catch (RemoteException e) { 750 Log.e(TAG, "Received remote exception for getFadeManagerConfigurationForFocusLoss:", 751 e); 752 throw e.rethrowFromSystemServer(); 753 754 } 755 } 756 } 757 758 @GuardedBy("mLock") isAudioPolicyRegisteredLocked()759 private boolean isAudioPolicyRegisteredLocked() { 760 return mStatus == POLICY_STATUS_REGISTERED; 761 } 762 policyReadyToUse()763 private boolean policyReadyToUse() { 764 synchronized (mLock) { 765 if (mStatus != POLICY_STATUS_REGISTERED) { 766 Log.e(TAG, "Cannot use unregistered AudioPolicy"); 767 return false; 768 } 769 if (mRegistrationId == null) { 770 Log.e(TAG, "Cannot use unregistered AudioPolicy"); 771 return false; 772 } 773 } 774 775 // Loopback|capture only need an audio projection, everything else need MODIFY_AUDIO_ROUTING 776 boolean canModifyAudioRouting = PackageManager.PERMISSION_GRANTED 777 == checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING); 778 779 boolean canInterceptCallAudio = PackageManager.PERMISSION_GRANTED 780 == checkCallingOrSelfPermission( 781 android.Manifest.permission.CALL_AUDIO_INTERCEPTION); 782 783 boolean canProjectAudio; 784 try { 785 canProjectAudio = mProjection != null && mProjection.getProjection().canProjectAudio(); 786 } catch (RemoteException e) { 787 Log.e(TAG, "Failed to check if MediaProjection#canProjectAudio"); 788 throw e.rethrowFromSystemServer(); 789 } 790 791 if (!((isLoopbackRenderPolicy() && canProjectAudio) 792 || (isCallRedirectionPolicy() && canInterceptCallAudio) 793 || canModifyAudioRouting)) { 794 Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid " 795 + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING or " 796 + "MediaProjection that can project audio."); 797 return false; 798 } 799 return true; 800 } 801 isLoopbackRenderPolicy()802 private boolean isLoopbackRenderPolicy() { 803 synchronized (mLock) { 804 return mConfig.mMixes.stream().allMatch(mix -> mix.getRouteFlags() 805 == (mix.ROUTE_FLAG_RENDER | mix.ROUTE_FLAG_LOOP_BACK)); 806 } 807 } 808 isCallRedirectionPolicy()809 private boolean isCallRedirectionPolicy() { 810 synchronized (mLock) { 811 for (AudioMix mix : mConfig.mMixes) { 812 if (mix.isForCallRedirection()) { 813 return true; 814 } 815 } 816 return false; 817 } 818 } 819 820 /** 821 * Returns {@link PackageManager#PERMISSION_GRANTED} if the caller has the given permission. 822 */ checkCallingOrSelfPermission(String permission)823 private @PackageManager.PermissionResult int checkCallingOrSelfPermission(String permission) { 824 if (mContext != null) { 825 return mContext.checkCallingOrSelfPermission(permission); 826 } 827 Slog.v(TAG, "Null context, checking permission via ActivityManager"); 828 int pid = Binder.getCallingPid(); 829 int uid = Binder.getCallingUid(); 830 try { 831 return ActivityManager.getService().checkPermission(permission, pid, uid); 832 } catch (RemoteException e) { 833 throw e.rethrowFromSystemServer(); 834 } 835 } 836 checkMixReadyToUse(AudioMix mix, boolean forTrack)837 private void checkMixReadyToUse(AudioMix mix, boolean forTrack) 838 throws IllegalArgumentException{ 839 if (mix == null) { 840 String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation" 841 : "Invalid null AudioMix for AudioRecord creation"; 842 throw new IllegalArgumentException(msg); 843 } 844 if (!mConfig.mMixes.contains(mix)) { 845 throw new IllegalArgumentException("Invalid mix: not part of this policy"); 846 } 847 if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK) 848 { 849 throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back"); 850 } 851 if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) { 852 throw new IllegalArgumentException( 853 "Invalid AudioMix: not defined for being a recording source"); 854 } 855 if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) { 856 throw new IllegalArgumentException( 857 "Invalid AudioMix: not defined for capturing playback"); 858 } 859 } 860 861 /** 862 * Returns the current behavior for audio focus-related ducking. 863 * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY} 864 */ getFocusDuckingBehavior()865 public int getFocusDuckingBehavior() { 866 return mConfig.mDuckingPolicy; 867 } 868 869 // Note on implementation: not part of the Builder as there can be only one registered policy 870 // that handles ducking but there can be multiple policies 871 /** 872 * Sets the behavior for audio focus-related ducking. 873 * There must be a focus listener if this policy is to handle ducking. 874 * @param behavior {@link #FOCUS_POLICY_DUCKING_IN_APP} or 875 * {@link #FOCUS_POLICY_DUCKING_IN_POLICY} 876 * @return {@link AudioManager#SUCCESS} or {@link AudioManager#ERROR} (for instance if there 877 * is already an audio policy that handles ducking). 878 * @throws IllegalArgumentException 879 * @throws IllegalStateException 880 */ setFocusDuckingBehavior(int behavior)881 public int setFocusDuckingBehavior(int behavior) 882 throws IllegalArgumentException, IllegalStateException { 883 if ((behavior != FOCUS_POLICY_DUCKING_IN_APP) 884 && (behavior != FOCUS_POLICY_DUCKING_IN_POLICY)) { 885 throw new IllegalArgumentException("Invalid ducking behavior " + behavior); 886 } 887 synchronized (mLock) { 888 if (mStatus != POLICY_STATUS_REGISTERED) { 889 throw new IllegalStateException( 890 "Cannot change ducking behavior for unregistered policy"); 891 } 892 if ((behavior == FOCUS_POLICY_DUCKING_IN_POLICY) 893 && (mFocusListener == null)) { 894 // there must be a focus listener if the policy handles ducking 895 throw new IllegalStateException( 896 "Cannot handle ducking without an audio focus listener"); 897 } 898 IAudioService service = getService(); 899 try { 900 final int status = service.setFocusPropertiesForPolicy(behavior /*duckingBehavior*/, 901 this.cb()); 902 if (status == AudioManager.SUCCESS) { 903 mConfig.mDuckingPolicy = behavior; 904 } 905 return status; 906 } catch (RemoteException e) { 907 Log.e(TAG, "Dead object in setFocusPropertiesForPolicy for behavior", e); 908 return AudioManager.ERROR; 909 } 910 } 911 } 912 913 /** 914 * Returns the list of entries in the focus stack. 915 * The list is ordered with increasing rank of focus ownership, where the last entry is at the 916 * top of the focus stack and is the current focus owner. 917 * @return the ordered list of focus owners 918 * @see AudioManager#registerAudioPolicy(AudioPolicy) 919 */ 920 @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) getFocusStack()921 public @NonNull List<AudioFocusInfo> getFocusStack() { 922 try { 923 return getService().getFocusStack(); 924 } catch (RemoteException e) { 925 throw e.rethrowFromSystemServer(); 926 } 927 } 928 929 /** 930 * Send AUDIOFOCUS_LOSS to a specific stack entry, causing it to be notified of the focus 931 * loss, and for it to exit the focus stack (its focus listener will not be invoked after that). 932 * This operation is only valid for a registered policy (with 933 * {@link AudioManager#registerAudioPolicy(AudioPolicy)}) that is also set as the policy focus 934 * listener (with {@link Builder#setAudioPolicyFocusListener(AudioPolicyFocusListener)}. 935 * @param focusLoser the stack entry that is exiting the stack through a focus loss 936 * @return false if the focusLoser wasn't found in the stack, true otherwise 937 * @throws IllegalStateException if used on an unregistered policy, or a registered policy 938 * with no {@link AudioPolicyFocusListener} set 939 * @see AudioManager#registerAudioPolicy(AudioPolicy) 940 * @see Builder#setAudioPolicyStatusListener(AudioPolicyStatusListener) 941 */ 942 @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) sendFocusLoss(@onNull AudioFocusInfo focusLoser)943 public boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser) throws IllegalStateException { 944 Objects.requireNonNull(focusLoser); 945 try { 946 return getService().sendFocusLoss(focusLoser, cb()); 947 } catch (RemoteException e) { 948 throw e.rethrowFromSystemServer(); 949 } 950 } 951 952 /** 953 * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}. 954 * Audio buffers recorded through the created instance will contain the mix of the audio 955 * streams that fed the given mixer. 956 * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with 957 * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy. 958 * @return a new {@link AudioRecord} instance whose data format is the one defined in the 959 * {@link AudioMix}, or null if this policy was not successfully registered 960 * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. 961 * @throws IllegalArgumentException 962 */ createAudioRecordSink(AudioMix mix)963 public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException { 964 if (!policyReadyToUse()) { 965 Log.e(TAG, "Cannot create AudioRecord sink for AudioMix"); 966 return null; 967 } 968 checkMixReadyToUse(mix, false/*not for an AudioTrack*/); 969 // create an AudioFormat from the mix format compatible with recording, as the mix 970 // was defined for playback 971 AudioFormat mixFormat = new AudioFormat.Builder(mix.getFormat()) 972 .setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask( 973 mix.getFormat().getChannelMask())) 974 .build(); 975 976 AudioAttributes.Builder ab = new AudioAttributes.Builder() 977 .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX) 978 .addTag(addressForTag(mix)) 979 .addTag(AudioRecord.SUBMIX_FIXED_VOLUME); 980 if (mix.isForCallRedirection()) { 981 ab.setForCallRedirection(); 982 } 983 // create the AudioRecord, configured for loop back, using the same format as the mix 984 AudioRecord ar = new AudioRecord(ab.build(), 985 mixFormat, 986 AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(), 987 // using stereo for buffer size to avoid the current poor support for masks 988 AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()), 989 AudioManager.AUDIO_SESSION_ID_GENERATE 990 ); 991 synchronized (mLock) { 992 if (mCaptors == null) { 993 mCaptors = new ArrayList<>(1); 994 } 995 mCaptors.add(new WeakReference<AudioRecord>(ar)); 996 } 997 return ar; 998 } 999 1000 /** 1001 * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}. 1002 * Audio buffers played through the created instance will be sent to the given mix 1003 * to be recorded through the recording APIs. 1004 * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with 1005 * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy. 1006 * @return a new {@link AudioTrack} instance whose data format is the one defined in the 1007 * {@link AudioMix}, or null if this policy was not successfully registered 1008 * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. 1009 * @throws IllegalArgumentException 1010 */ createAudioTrackSource(AudioMix mix)1011 public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException { 1012 if (!policyReadyToUse()) { 1013 Log.e(TAG, "Cannot create AudioTrack source for AudioMix"); 1014 return null; 1015 } 1016 checkMixReadyToUse(mix, true/*for an AudioTrack*/); 1017 // create the AudioTrack, configured for loop back, using the same format as the mix 1018 AudioAttributes.Builder ab = new AudioAttributes.Builder() 1019 .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE) 1020 .addTag(addressForTag(mix)); 1021 if (mix.isForCallRedirection()) { 1022 ab.setForCallRedirection(); 1023 } 1024 AudioTrack at = new AudioTrack(ab.build(), 1025 mix.getFormat(), 1026 AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(), 1027 mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()), 1028 AudioTrack.MODE_STREAM, 1029 AudioManager.AUDIO_SESSION_ID_GENERATE 1030 ); 1031 synchronized (mLock) { 1032 if (mInjectors == null) { 1033 mInjectors = new ArrayList<>(1); 1034 } 1035 mInjectors.add(new WeakReference<AudioTrack>(at)); 1036 } 1037 return at; 1038 } 1039 1040 /** 1041 * @hide 1042 */ invalidateCaptorsAndInjectors()1043 public void invalidateCaptorsAndInjectors() { 1044 if (!policyReadyToUse()) { 1045 return; 1046 } 1047 synchronized (mLock) { 1048 if (mInjectors != null) { 1049 for (final WeakReference<AudioTrack> weakTrack : mInjectors) { 1050 final AudioTrack track = weakTrack.get(); 1051 if (track == null) { 1052 continue; 1053 } 1054 try { 1055 // TODO: add synchronous versions 1056 track.stop(); 1057 track.flush(); 1058 } catch (IllegalStateException e) { 1059 // ignore exception, AudioTrack could have already been stopped or 1060 // released by the user of the AudioPolicy 1061 } 1062 } 1063 mInjectors.clear(); 1064 } 1065 if (mCaptors != null) { 1066 for (final WeakReference<AudioRecord> weakRecord : mCaptors) { 1067 final AudioRecord record = weakRecord.get(); 1068 if (record == null) { 1069 continue; 1070 } 1071 try { 1072 // TODO: if needed: implement an invalidate method 1073 record.stop(); 1074 } catch (IllegalStateException e) { 1075 // ignore exception, AudioRecord could have already been stopped or 1076 // released by the user of the AudioPolicy 1077 } 1078 } 1079 mCaptors.clear(); 1080 } 1081 } 1082 } 1083 getStatus()1084 public int getStatus() { 1085 return mStatus; 1086 } 1087 1088 public static abstract class AudioPolicyStatusListener { onStatusChange()1089 public void onStatusChange() {} onMixStateUpdate(AudioMix mix)1090 public void onMixStateUpdate(AudioMix mix) {} 1091 } 1092 1093 public static abstract class AudioPolicyFocusListener { onAudioFocusGrant(AudioFocusInfo afi, int requestResult)1094 public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {} onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified)1095 public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {} 1096 /** 1097 * Called whenever an application requests audio focus. 1098 * Only ever called if the {@link AudioPolicy} was built with 1099 * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}. 1100 * @param afi information about the focus request and the requester 1101 * @param requestResult deprecated after the addition of 1102 * {@link AudioManager#setFocusRequestResult(AudioFocusInfo, int, AudioPolicy)} 1103 * in Android P, always equal to {@link #AUDIOFOCUS_REQUEST_GRANTED}. 1104 */ onAudioFocusRequest(AudioFocusInfo afi, int requestResult)1105 public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {} 1106 /** 1107 * Called whenever an application abandons audio focus. 1108 * Only ever called if the {@link AudioPolicy} was built with 1109 * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}. 1110 * @param afi information about the focus request being abandoned and the original 1111 * requester. 1112 */ onAudioFocusAbandon(AudioFocusInfo afi)1113 public void onAudioFocusAbandon(AudioFocusInfo afi) {} 1114 } 1115 1116 /** 1117 * Callback class to receive volume change-related events. 1118 * See {@link #Builder.setAudioPolicyVolumeCallback(AudioPolicyCallback)} to configure the 1119 * {@link AudioPolicy} to receive those events. 1120 * 1121 */ 1122 public static abstract class AudioPolicyVolumeCallback { AudioPolicyVolumeCallback()1123 public AudioPolicyVolumeCallback() {} 1124 /** 1125 * Called when volume key-related changes are triggered, on the key down event. 1126 * @param adjustment the type of volume adjustment for the key. 1127 */ onVolumeAdjustment(@udioManager.VolumeAdjustment int adjustment)1128 public void onVolumeAdjustment(@AudioManager.VolumeAdjustment int adjustment) {} 1129 } 1130 onPolicyStatusChange()1131 private void onPolicyStatusChange() { 1132 if (mStatusListener != null) { 1133 mStatusListener.onStatusChange(); 1134 } 1135 } 1136 1137 //================================================== 1138 // Callback interface 1139 1140 /** @hide */ cb()1141 public IAudioPolicyCallback cb() { return mPolicyCb; } 1142 1143 private final IAudioPolicyCallback mPolicyCb = new IAudioPolicyCallback.Stub() { 1144 1145 public void notifyAudioFocusGrant(AudioFocusInfo afi, int requestResult) { 1146 sendMsg(MSG_FOCUS_GRANT, afi, requestResult); 1147 if (DEBUG) { 1148 Log.v(TAG, "notifyAudioFocusGrant: pack=" + afi.getPackageName() + " client=" 1149 + afi.getClientId() + "reqRes=" + requestResult); 1150 } 1151 } 1152 1153 public void notifyAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) { 1154 sendMsg(MSG_FOCUS_LOSS, afi, wasNotified ? 1 : 0); 1155 if (DEBUG) { 1156 Log.v(TAG, "notifyAudioFocusLoss: pack=" + afi.getPackageName() + " client=" 1157 + afi.getClientId() + "wasNotified=" + wasNotified); 1158 } 1159 } 1160 1161 public void notifyAudioFocusRequest(AudioFocusInfo afi, int requestResult) { 1162 sendMsg(MSG_FOCUS_REQUEST, afi, requestResult); 1163 if (DEBUG) { 1164 Log.v(TAG, "notifyAudioFocusRequest: pack=" + afi.getPackageName() + " client=" 1165 + afi.getClientId() + " gen=" + afi.getGen()); 1166 } 1167 } 1168 1169 public void notifyAudioFocusAbandon(AudioFocusInfo afi) { 1170 sendMsg(MSG_FOCUS_ABANDON, afi, 0 /* ignored */); 1171 if (DEBUG) { 1172 Log.v(TAG, "notifyAudioFocusAbandon: pack=" + afi.getPackageName() + " client=" 1173 + afi.getClientId()); 1174 } 1175 } 1176 1177 public void notifyMixStateUpdate(String regId, int state) { 1178 for (AudioMix mix : mConfig.getMixes()) { 1179 if (mix.getRegistration().equals(regId)) { 1180 mix.mMixState = state; 1181 sendMsg(MSG_MIX_STATE_UPDATE, mix, 0/*ignored*/); 1182 if (DEBUG) { 1183 Log.v(TAG, "notifyMixStateUpdate: regId=" + regId + " state=" + state); 1184 } 1185 } 1186 } 1187 } 1188 1189 public void notifyVolumeAdjust(int adjustment) { 1190 sendMsg(MSG_VOL_ADJUST, null /* ignored */, adjustment); 1191 if (DEBUG) { 1192 Log.v(TAG, "notifyVolumeAdjust: " + adjustment); 1193 } 1194 } 1195 1196 public void notifyUnregistration() { 1197 setRegistration(null); 1198 } 1199 }; 1200 1201 //================================================== 1202 // Event handling 1203 private final EventHandler mEventHandler; 1204 private final static int MSG_POLICY_STATUS_CHANGE = 0; 1205 private final static int MSG_FOCUS_GRANT = 1; 1206 private final static int MSG_FOCUS_LOSS = 2; 1207 private final static int MSG_MIX_STATE_UPDATE = 3; 1208 private final static int MSG_FOCUS_REQUEST = 4; 1209 private final static int MSG_FOCUS_ABANDON = 5; 1210 private final static int MSG_VOL_ADJUST = 6; 1211 1212 private class EventHandler extends Handler { EventHandler(AudioPolicy ap, Looper looper)1213 public EventHandler(AudioPolicy ap, Looper looper) { 1214 super(looper); 1215 } 1216 1217 @Override handleMessage(Message msg)1218 public void handleMessage(Message msg) { 1219 switch(msg.what) { 1220 case MSG_POLICY_STATUS_CHANGE: 1221 onPolicyStatusChange(); 1222 break; 1223 case MSG_FOCUS_GRANT: 1224 if (mFocusListener != null) { 1225 mFocusListener.onAudioFocusGrant( 1226 (AudioFocusInfo) msg.obj, msg.arg1); 1227 } 1228 break; 1229 case MSG_FOCUS_LOSS: 1230 if (mFocusListener != null) { 1231 mFocusListener.onAudioFocusLoss( 1232 (AudioFocusInfo) msg.obj, msg.arg1 != 0); 1233 } 1234 break; 1235 case MSG_MIX_STATE_UPDATE: 1236 if (mStatusListener != null) { 1237 mStatusListener.onMixStateUpdate((AudioMix) msg.obj); 1238 } 1239 break; 1240 case MSG_FOCUS_REQUEST: 1241 if (mFocusListener != null) { 1242 mFocusListener.onAudioFocusRequest((AudioFocusInfo) msg.obj, msg.arg1); 1243 } else { // should never be null, but don't crash 1244 Log.e(TAG, "Invalid null focus listener for focus request event"); 1245 } 1246 break; 1247 case MSG_FOCUS_ABANDON: 1248 if (mFocusListener != null) { // should never be null 1249 mFocusListener.onAudioFocusAbandon((AudioFocusInfo) msg.obj); 1250 } else { // should never be null, but don't crash 1251 Log.e(TAG, "Invalid null focus listener for focus abandon event"); 1252 } 1253 break; 1254 case MSG_VOL_ADJUST: 1255 if (mVolCb != null) { 1256 mVolCb.onVolumeAdjustment(msg.arg1); 1257 } else { // should never be null, but don't crash 1258 Log.e(TAG, "Invalid null volume event"); 1259 } 1260 break; 1261 default: 1262 Log.e(TAG, "Unknown event " + msg.what); 1263 } 1264 } 1265 } 1266 1267 //========================================================== 1268 // Utils addressForTag(AudioMix mix)1269 private static String addressForTag(AudioMix mix) { 1270 return "addr=" + mix.getRegistration(); 1271 } 1272 sendMsg(int msg)1273 private void sendMsg(int msg) { 1274 if (mEventHandler != null) { 1275 mEventHandler.sendEmptyMessage(msg); 1276 } 1277 } 1278 sendMsg(int msg, Object obj, int i)1279 private void sendMsg(int msg, Object obj, int i) { 1280 if (mEventHandler != null) { 1281 mEventHandler.sendMessage( 1282 mEventHandler.obtainMessage(msg, i /*arg1*/, 0 /*arg2, ignored*/, obj)); 1283 } 1284 } 1285 1286 private static IAudioService sService; 1287 getService()1288 private static IAudioService getService() 1289 { 1290 if (sService != null) { 1291 return sService; 1292 } 1293 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); 1294 sService = IAudioService.Stub.asInterface(b); 1295 return sService; 1296 } 1297 toLogFriendlyString()1298 public String toLogFriendlyString() { 1299 String textDump = new String("android.media.audiopolicy.AudioPolicy:\n"); 1300 textDump += "config=" + mConfig.toLogFriendlyString(); 1301 return (textDump); 1302 } 1303 1304 /** @hide */ 1305 @IntDef({ 1306 POLICY_STATUS_REGISTERED, 1307 POLICY_STATUS_UNREGISTERED 1308 }) 1309 @Retention(RetentionPolicy.SOURCE) 1310 public @interface PolicyStatus {} 1311 } 1312