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 import java.util.List; 46 47 /** 48 * @hide 49 * AudioPolicy provides access to the management of audio routing and audio focus. 50 */ 51 @SystemApi 52 public class AudioPolicy { 53 54 private static final String TAG = "AudioPolicy"; 55 private static final boolean DEBUG = false; 56 private final Object mLock = new Object(); 57 58 /** 59 * The status of an audio policy that is valid but cannot be used because it is not registered. 60 */ 61 @SystemApi 62 public static final int POLICY_STATUS_UNREGISTERED = 1; 63 /** 64 * The status of an audio policy that is valid, successfully registered and thus active. 65 */ 66 @SystemApi 67 public static final int POLICY_STATUS_REGISTERED = 2; 68 69 private int mStatus; 70 private String mRegistrationId; 71 private AudioPolicyStatusListener mStatusListener; 72 private boolean mIsFocusPolicy; 73 74 /** 75 * The behavior of a policy with regards to audio focus where it relies on the application 76 * to do the ducking, the is the legacy and default behavior. 77 */ 78 @SystemApi 79 public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; 80 public static final int FOCUS_POLICY_DUCKING_DEFAULT = FOCUS_POLICY_DUCKING_IN_APP; 81 /** 82 * The behavior of a policy with regards to audio focus where it handles ducking instead 83 * of the application losing focus and being signaled it can duck (as communicated by 84 * {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}). 85 * <br>Can only be used after having set a listener with 86 * {@link AudioPolicy#setAudioPolicyFocusListener(AudioPolicyFocusListener)}. 87 */ 88 @SystemApi 89 public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; 90 91 private AudioPolicyFocusListener mFocusListener; 92 93 private final AudioPolicyVolumeCallback mVolCb; 94 95 private Context mContext; 96 97 private AudioPolicyConfig mConfig; 98 99 /** @hide */ 100 public AudioPolicyConfig getConfig() { return mConfig; } 101 /** @hide */ 102 public boolean hasFocusListener() { return mFocusListener != null; } 103 /** @hide */ 104 public boolean isFocusPolicy() { return mIsFocusPolicy; } 105 /** @hide */ 106 public boolean isVolumeController() { return mVolCb != null; } 107 108 /** 109 * The parameter is guaranteed non-null through the Builder 110 */ 111 private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper, 112 AudioPolicyFocusListener fl, AudioPolicyStatusListener sl, boolean isFocusPolicy, 113 AudioPolicyVolumeCallback vc) { 114 mConfig = config; 115 mStatus = POLICY_STATUS_UNREGISTERED; 116 mContext = context; 117 if (looper == null) { 118 looper = Looper.getMainLooper(); 119 } 120 if (looper != null) { 121 mEventHandler = new EventHandler(this, looper); 122 } else { 123 mEventHandler = null; 124 Log.e(TAG, "No event handler due to looper without a thread"); 125 } 126 mFocusListener = fl; 127 mStatusListener = sl; 128 mIsFocusPolicy = isFocusPolicy; 129 mVolCb = vc; 130 } 131 132 /** 133 * Builder class for {@link AudioPolicy} objects. 134 * By default the policy to be created doesn't govern audio focus decisions. 135 */ 136 @SystemApi 137 public static class Builder { 138 private ArrayList<AudioMix> mMixes; 139 private Context mContext; 140 private Looper mLooper; 141 private AudioPolicyFocusListener mFocusListener; 142 private AudioPolicyStatusListener mStatusListener; 143 private boolean mIsFocusPolicy = false; 144 private AudioPolicyVolumeCallback mVolCb; 145 146 /** 147 * Constructs a new Builder with no audio mixes. 148 * @param context the context for the policy 149 */ 150 @SystemApi 151 public Builder(Context context) { 152 mMixes = new ArrayList<AudioMix>(); 153 mContext = context; 154 } 155 156 /** 157 * Add an {@link AudioMix} to be part of the audio policy being built. 158 * @param mix a non-null {@link AudioMix} to be part of the audio policy. 159 * @return the same Builder instance. 160 * @throws IllegalArgumentException 161 */ 162 @SystemApi 163 public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException { 164 if (mix == null) { 165 throw new IllegalArgumentException("Illegal null AudioMix argument"); 166 } 167 mMixes.add(mix); 168 return this; 169 } 170 171 /** 172 * Sets the {@link Looper} on which to run the event loop. 173 * @param looper a non-null specific Looper. 174 * @return the same Builder instance. 175 * @throws IllegalArgumentException 176 */ 177 @SystemApi 178 public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException { 179 if (looper == null) { 180 throw new IllegalArgumentException("Illegal null Looper argument"); 181 } 182 mLooper = looper; 183 return this; 184 } 185 186 /** 187 * Sets the audio focus listener for the policy. 188 * @param l a {@link AudioPolicy.AudioPolicyFocusListener} 189 */ 190 @SystemApi 191 public void setAudioPolicyFocusListener(AudioPolicyFocusListener l) { 192 mFocusListener = l; 193 } 194 195 /** 196 * Declares whether this policy will grant and deny audio focus through 197 * the {@link AudioPolicy.AudioPolicyFocusListener}. 198 * If set to {@code true}, it is mandatory to set an 199 * {@link AudioPolicy.AudioPolicyFocusListener} in order to successfully build 200 * an {@code AudioPolicy} instance. 201 * @param enforce true if the policy will govern audio focus decisions. 202 * @return the same Builder instance. 203 */ 204 @SystemApi 205 public Builder setIsAudioFocusPolicy(boolean isFocusPolicy) { 206 mIsFocusPolicy = isFocusPolicy; 207 return this; 208 } 209 210 /** 211 * Sets the audio policy status listener. 212 * @param l a {@link AudioPolicy.AudioPolicyStatusListener} 213 */ 214 @SystemApi 215 public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) { 216 mStatusListener = l; 217 } 218 219 @SystemApi 220 /** 221 * Sets the callback to receive all volume key-related events. 222 * The callback will only be called if the device is configured to handle volume events 223 * in the PhoneWindowManager (see config_handleVolumeKeysInWindowManager) 224 * @param vc 225 * @return the same Builder instance. 226 */ 227 public Builder setAudioPolicyVolumeCallback(@NonNull AudioPolicyVolumeCallback vc) { 228 if (vc == null) { 229 throw new IllegalArgumentException("Invalid null volume callback"); 230 } 231 mVolCb = vc; 232 return this; 233 } 234 235 /** 236 * Combines all of the attributes that have been set on this {@code Builder} and returns a 237 * new {@link AudioPolicy} object. 238 * @return a new {@code AudioPolicy} object. 239 * @throws IllegalStateException if there is no 240 * {@link AudioPolicy.AudioPolicyStatusListener} but the policy was configured 241 * as an audio focus policy with {@link #setIsAudioFocusPolicy(boolean)}. 242 */ 243 @SystemApi 244 public AudioPolicy build() { 245 if (mStatusListener != null) { 246 // the AudioPolicy status listener includes updates on each mix activity state 247 for (AudioMix mix : mMixes) { 248 mix.mCallbackFlags |= AudioMix.CALLBACK_FLAG_NOTIFY_ACTIVITY; 249 } 250 } 251 if (mIsFocusPolicy && mFocusListener == null) { 252 throw new IllegalStateException("Cannot be a focus policy without " 253 + "an AudioPolicyFocusListener"); 254 } 255 return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper, 256 mFocusListener, mStatusListener, mIsFocusPolicy, mVolCb); 257 } 258 } 259 260 /** 261 * @hide 262 * Update the current configuration of the set of audio mixes by adding new ones, while 263 * keeping the policy registered. 264 * This method can only be called on a registered policy. 265 * @param mixes the list of {@link AudioMix} to add 266 * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR} 267 * otherwise. 268 */ 269 @SystemApi 270 public int attachMixes(@NonNull List<AudioMix> mixes) { 271 if (mixes == null) { 272 throw new IllegalArgumentException("Illegal null list of AudioMix"); 273 } 274 synchronized (mLock) { 275 if (mStatus != POLICY_STATUS_REGISTERED) { 276 throw new IllegalStateException("Cannot alter unregistered AudioPolicy"); 277 } 278 final ArrayList<AudioMix> zeMixes = new ArrayList<AudioMix>(mixes.size()); 279 for (AudioMix mix : mixes) { 280 if (mix == null) { 281 throw new IllegalArgumentException("Illegal null AudioMix in attachMixes"); 282 } else { 283 zeMixes.add(mix); 284 } 285 } 286 final AudioPolicyConfig cfg = new AudioPolicyConfig(zeMixes); 287 IAudioService service = getService(); 288 try { 289 final int status = service.addMixForPolicy(cfg, this.cb()); 290 if (status == AudioManager.SUCCESS) { 291 mConfig.add(zeMixes); 292 } 293 return status; 294 } catch (RemoteException e) { 295 Log.e(TAG, "Dead object in attachMixes", e); 296 return AudioManager.ERROR; 297 } 298 } 299 } 300 301 /** 302 * @hide 303 * Update the current configuration of the set of audio mixes by removing some, while 304 * keeping the policy registered. 305 * This method can only be called on a registered policy. 306 * @param mixes the list of {@link AudioMix} to remove 307 * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR} 308 * otherwise. 309 */ 310 @SystemApi 311 public int detachMixes(@NonNull List<AudioMix> mixes) { 312 if (mixes == null) { 313 throw new IllegalArgumentException("Illegal null list of AudioMix"); 314 } 315 synchronized (mLock) { 316 if (mStatus != POLICY_STATUS_REGISTERED) { 317 throw new IllegalStateException("Cannot alter unregistered AudioPolicy"); 318 } 319 final ArrayList<AudioMix> zeMixes = new ArrayList<AudioMix>(mixes.size()); 320 for (AudioMix mix : mixes) { 321 if (mix == null) { 322 throw new IllegalArgumentException("Illegal null AudioMix in detachMixes"); 323 // TODO also check mix is currently contained in list of mixes 324 } else { 325 zeMixes.add(mix); 326 } 327 } 328 final AudioPolicyConfig cfg = new AudioPolicyConfig(zeMixes); 329 IAudioService service = getService(); 330 try { 331 final int status = service.removeMixForPolicy(cfg, this.cb()); 332 if (status == AudioManager.SUCCESS) { 333 mConfig.remove(zeMixes); 334 } 335 return status; 336 } catch (RemoteException e) { 337 Log.e(TAG, "Dead object in detachMixes", e); 338 return AudioManager.ERROR; 339 } 340 } 341 } 342 343 public void setRegistration(String regId) { 344 synchronized (mLock) { 345 mRegistrationId = regId; 346 mConfig.setRegistration(regId); 347 if (regId != null) { 348 mStatus = POLICY_STATUS_REGISTERED; 349 } else { 350 mStatus = POLICY_STATUS_UNREGISTERED; 351 } 352 } 353 sendMsg(MSG_POLICY_STATUS_CHANGE); 354 } 355 356 private boolean policyReadyToUse() { 357 synchronized (mLock) { 358 if (mStatus != POLICY_STATUS_REGISTERED) { 359 Log.e(TAG, "Cannot use unregistered AudioPolicy"); 360 return false; 361 } 362 if (mContext == null) { 363 Log.e(TAG, "Cannot use AudioPolicy without context"); 364 return false; 365 } 366 if (mRegistrationId == null) { 367 Log.e(TAG, "Cannot use unregistered AudioPolicy"); 368 return false; 369 } 370 } 371 if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( 372 android.Manifest.permission.MODIFY_AUDIO_ROUTING))) { 373 Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid " 374 + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING"); 375 return false; 376 } 377 return true; 378 } 379 380 private void checkMixReadyToUse(AudioMix mix, boolean forTrack) 381 throws IllegalArgumentException{ 382 if (mix == null) { 383 String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation" 384 : "Invalid null AudioMix for AudioRecord creation"; 385 throw new IllegalArgumentException(msg); 386 } 387 if (!mConfig.mMixes.contains(mix)) { 388 throw new IllegalArgumentException("Invalid mix: not part of this policy"); 389 } 390 if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK) 391 { 392 throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back"); 393 } 394 if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) { 395 throw new IllegalArgumentException( 396 "Invalid AudioMix: not defined for being a recording source"); 397 } 398 if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) { 399 throw new IllegalArgumentException( 400 "Invalid AudioMix: not defined for capturing playback"); 401 } 402 } 403 404 /** 405 * Returns the current behavior for audio focus-related ducking. 406 * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY} 407 */ 408 @SystemApi 409 public int getFocusDuckingBehavior() { 410 return mConfig.mDuckingPolicy; 411 } 412 413 // Note on implementation: not part of the Builder as there can be only one registered policy 414 // that handles ducking but there can be multiple policies 415 /** 416 * Sets the behavior for audio focus-related ducking. 417 * There must be a focus listener if this policy is to handle ducking. 418 * @param behavior {@link #FOCUS_POLICY_DUCKING_IN_APP} or 419 * {@link #FOCUS_POLICY_DUCKING_IN_POLICY} 420 * @return {@link AudioManager#SUCCESS} or {@link AudioManager#ERROR} (for instance if there 421 * is already an audio policy that handles ducking). 422 * @throws IllegalArgumentException 423 * @throws IllegalStateException 424 */ 425 @SystemApi 426 public int setFocusDuckingBehavior(int behavior) 427 throws IllegalArgumentException, IllegalStateException { 428 if ((behavior != FOCUS_POLICY_DUCKING_IN_APP) 429 && (behavior != FOCUS_POLICY_DUCKING_IN_POLICY)) { 430 throw new IllegalArgumentException("Invalid ducking behavior " + behavior); 431 } 432 synchronized (mLock) { 433 if (mStatus != POLICY_STATUS_REGISTERED) { 434 throw new IllegalStateException( 435 "Cannot change ducking behavior for unregistered policy"); 436 } 437 if ((behavior == FOCUS_POLICY_DUCKING_IN_POLICY) 438 && (mFocusListener == null)) { 439 // there must be a focus listener if the policy handles ducking 440 throw new IllegalStateException( 441 "Cannot handle ducking without an audio focus listener"); 442 } 443 IAudioService service = getService(); 444 try { 445 final int status = service.setFocusPropertiesForPolicy(behavior /*duckingBehavior*/, 446 this.cb()); 447 if (status == AudioManager.SUCCESS) { 448 mConfig.mDuckingPolicy = behavior; 449 } 450 return status; 451 } catch (RemoteException e) { 452 Log.e(TAG, "Dead object in setFocusPropertiesForPolicy for behavior", e); 453 return AudioManager.ERROR; 454 } 455 } 456 } 457 458 /** 459 * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}. 460 * Audio buffers recorded through the created instance will contain the mix of the audio 461 * streams that fed the given mixer. 462 * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with 463 * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy. 464 * @return a new {@link AudioRecord} instance whose data format is the one defined in the 465 * {@link AudioMix}, or null if this policy was not successfully registered 466 * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. 467 * @throws IllegalArgumentException 468 */ 469 @SystemApi 470 public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException { 471 if (!policyReadyToUse()) { 472 Log.e(TAG, "Cannot create AudioRecord sink for AudioMix"); 473 return null; 474 } 475 checkMixReadyToUse(mix, false/*not for an AudioTrack*/); 476 // create an AudioFormat from the mix format compatible with recording, as the mix 477 // was defined for playback 478 AudioFormat mixFormat = new AudioFormat.Builder(mix.getFormat()) 479 .setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask( 480 mix.getFormat().getChannelMask())) 481 .build(); 482 // create the AudioRecord, configured for loop back, using the same format as the mix 483 AudioRecord ar = new AudioRecord( 484 new AudioAttributes.Builder() 485 .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX) 486 .addTag(addressForTag(mix)) 487 .addTag(AudioRecord.SUBMIX_FIXED_VOLUME) 488 .build(), 489 mixFormat, 490 AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(), 491 // using stereo for buffer size to avoid the current poor support for masks 492 AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()), 493 AudioManager.AUDIO_SESSION_ID_GENERATE 494 ); 495 return ar; 496 } 497 498 /** 499 * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}. 500 * Audio buffers played through the created instance will be sent to the given mix 501 * to be recorded through the recording APIs. 502 * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with 503 * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy. 504 * @return a new {@link AudioTrack} instance whose data format is the one defined in the 505 * {@link AudioMix}, or null if this policy was not successfully registered 506 * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. 507 * @throws IllegalArgumentException 508 */ 509 @SystemApi 510 public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException { 511 if (!policyReadyToUse()) { 512 Log.e(TAG, "Cannot create AudioTrack source for AudioMix"); 513 return null; 514 } 515 checkMixReadyToUse(mix, true/*for an AudioTrack*/); 516 // create the AudioTrack, configured for loop back, using the same format as the mix 517 AudioTrack at = new AudioTrack( 518 new AudioAttributes.Builder() 519 .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE) 520 .addTag(addressForTag(mix)) 521 .build(), 522 mix.getFormat(), 523 AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(), 524 mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()), 525 AudioTrack.MODE_STREAM, 526 AudioManager.AUDIO_SESSION_ID_GENERATE 527 ); 528 return at; 529 } 530 531 @SystemApi 532 public int getStatus() { 533 return mStatus; 534 } 535 536 @SystemApi 537 public static abstract class AudioPolicyStatusListener { 538 public void onStatusChange() {} 539 public void onMixStateUpdate(AudioMix mix) {} 540 } 541 542 @SystemApi 543 public static abstract class AudioPolicyFocusListener { 544 public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {} 545 public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {} 546 /** 547 * Called whenever an application requests audio focus. 548 * Only ever called if the {@link AudioPolicy} was built with 549 * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}. 550 * @param afi information about the focus request and the requester 551 * @param requestResult deprecated after the addition of 552 * {@link AudioManager#setFocusRequestResult(AudioFocusInfo, int, AudioPolicy)} 553 * in Android P, always equal to {@link #AUDIOFOCUS_REQUEST_GRANTED}. 554 */ 555 public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {} 556 /** 557 * Called whenever an application abandons audio focus. 558 * Only ever called if the {@link AudioPolicy} was built with 559 * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}. 560 * @param afi information about the focus request being abandoned and the original 561 * requester. 562 */ 563 public void onAudioFocusAbandon(AudioFocusInfo afi) {} 564 } 565 566 @SystemApi 567 /** 568 * Callback class to receive volume change-related events. 569 * See {@link #Builder.setAudioPolicyVolumeCallback(AudioPolicyCallback)} to configure the 570 * {@link AudioPolicy} to receive those events. 571 * 572 */ 573 public static abstract class AudioPolicyVolumeCallback { 574 /** @hide */ 575 public AudioPolicyVolumeCallback() {} 576 /** 577 * Called when volume key-related changes are triggered, on the key down event. 578 * @param adjustment the type of volume adjustment for the key. 579 */ 580 public void onVolumeAdjustment(@AudioManager.VolumeAdjustment int adjustment) {} 581 } 582 583 private void onPolicyStatusChange() { 584 AudioPolicyStatusListener l; 585 synchronized (mLock) { 586 if (mStatusListener == null) { 587 return; 588 } 589 l = mStatusListener; 590 } 591 l.onStatusChange(); 592 } 593 594 //================================================== 595 // Callback interface 596 597 /** @hide */ 598 public IAudioPolicyCallback cb() { return mPolicyCb; } 599 600 private final IAudioPolicyCallback mPolicyCb = new IAudioPolicyCallback.Stub() { 601 602 public void notifyAudioFocusGrant(AudioFocusInfo afi, int requestResult) { 603 sendMsg(MSG_FOCUS_GRANT, afi, requestResult); 604 if (DEBUG) { 605 Log.v(TAG, "notifyAudioFocusGrant: pack=" + afi.getPackageName() + " client=" 606 + afi.getClientId() + "reqRes=" + requestResult); 607 } 608 } 609 610 public void notifyAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) { 611 sendMsg(MSG_FOCUS_LOSS, afi, wasNotified ? 1 : 0); 612 if (DEBUG) { 613 Log.v(TAG, "notifyAudioFocusLoss: pack=" + afi.getPackageName() + " client=" 614 + afi.getClientId() + "wasNotified=" + wasNotified); 615 } 616 } 617 618 public void notifyAudioFocusRequest(AudioFocusInfo afi, int requestResult) { 619 sendMsg(MSG_FOCUS_REQUEST, afi, requestResult); 620 if (DEBUG) { 621 Log.v(TAG, "notifyAudioFocusRequest: pack=" + afi.getPackageName() + " client=" 622 + afi.getClientId() + " gen=" + afi.getGen()); 623 } 624 } 625 626 public void notifyAudioFocusAbandon(AudioFocusInfo afi) { 627 sendMsg(MSG_FOCUS_ABANDON, afi, 0 /* ignored */); 628 if (DEBUG) { 629 Log.v(TAG, "notifyAudioFocusAbandon: pack=" + afi.getPackageName() + " client=" 630 + afi.getClientId()); 631 } 632 } 633 634 public void notifyMixStateUpdate(String regId, int state) { 635 for (AudioMix mix : mConfig.getMixes()) { 636 if (mix.getRegistration().equals(regId)) { 637 mix.mMixState = state; 638 sendMsg(MSG_MIX_STATE_UPDATE, mix, 0/*ignored*/); 639 if (DEBUG) { 640 Log.v(TAG, "notifyMixStateUpdate: regId=" + regId + " state=" + state); 641 } 642 } 643 } 644 } 645 646 public void notifyVolumeAdjust(int adjustment) { 647 sendMsg(MSG_VOL_ADJUST, null /* ignored */, adjustment); 648 if (DEBUG) { 649 Log.v(TAG, "notifyVolumeAdjust: " + adjustment); 650 } 651 } 652 }; 653 654 //================================================== 655 // Event handling 656 private final EventHandler mEventHandler; 657 private final static int MSG_POLICY_STATUS_CHANGE = 0; 658 private final static int MSG_FOCUS_GRANT = 1; 659 private final static int MSG_FOCUS_LOSS = 2; 660 private final static int MSG_MIX_STATE_UPDATE = 3; 661 private final static int MSG_FOCUS_REQUEST = 4; 662 private final static int MSG_FOCUS_ABANDON = 5; 663 private final static int MSG_VOL_ADJUST = 6; 664 665 private class EventHandler extends Handler { 666 public EventHandler(AudioPolicy ap, Looper looper) { 667 super(looper); 668 } 669 670 @Override 671 public void handleMessage(Message msg) { 672 switch(msg.what) { 673 case MSG_POLICY_STATUS_CHANGE: 674 onPolicyStatusChange(); 675 break; 676 case MSG_FOCUS_GRANT: 677 if (mFocusListener != null) { 678 mFocusListener.onAudioFocusGrant( 679 (AudioFocusInfo) msg.obj, msg.arg1); 680 } 681 break; 682 case MSG_FOCUS_LOSS: 683 if (mFocusListener != null) { 684 mFocusListener.onAudioFocusLoss( 685 (AudioFocusInfo) msg.obj, msg.arg1 != 0); 686 } 687 break; 688 case MSG_MIX_STATE_UPDATE: 689 if (mStatusListener != null) { 690 mStatusListener.onMixStateUpdate((AudioMix) msg.obj); 691 } 692 break; 693 case MSG_FOCUS_REQUEST: 694 if (mFocusListener != null) { 695 mFocusListener.onAudioFocusRequest((AudioFocusInfo) msg.obj, msg.arg1); 696 } else { // should never be null, but don't crash 697 Log.e(TAG, "Invalid null focus listener for focus request event"); 698 } 699 break; 700 case MSG_FOCUS_ABANDON: 701 if (mFocusListener != null) { // should never be null 702 mFocusListener.onAudioFocusAbandon((AudioFocusInfo) msg.obj); 703 } else { // should never be null, but don't crash 704 Log.e(TAG, "Invalid null focus listener for focus abandon event"); 705 } 706 break; 707 case MSG_VOL_ADJUST: 708 if (mVolCb != null) { 709 mVolCb.onVolumeAdjustment(msg.arg1); 710 } else { // should never be null, but don't crash 711 Log.e(TAG, "Invalid null volume event"); 712 } 713 break; 714 default: 715 Log.e(TAG, "Unknown event " + msg.what); 716 } 717 } 718 } 719 720 //========================================================== 721 // Utils 722 private static String addressForTag(AudioMix mix) { 723 return "addr=" + mix.getRegistration(); 724 } 725 726 private void sendMsg(int msg) { 727 if (mEventHandler != null) { 728 mEventHandler.sendEmptyMessage(msg); 729 } 730 } 731 732 private void sendMsg(int msg, Object obj, int i) { 733 if (mEventHandler != null) { 734 mEventHandler.sendMessage( 735 mEventHandler.obtainMessage(msg, i /*arg1*/, 0 /*arg2, ignored*/, obj)); 736 } 737 } 738 739 private static IAudioService sService; 740 741 private static IAudioService getService() 742 { 743 if (sService != null) { 744 return sService; 745 } 746 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); 747 sService = IAudioService.Stub.asInterface(b); 748 return sService; 749 } 750 751 public String toLogFriendlyString() { 752 String textDump = new String("android.media.audiopolicy.AudioPolicy:\n"); 753 textDump += "config=" + mConfig.toLogFriendlyString(); 754 return (textDump); 755 } 756 757 /** @hide */ 758 @IntDef({ 759 POLICY_STATUS_REGISTERED, 760 POLICY_STATUS_UNREGISTERED 761 }) 762 @Retention(RetentionPolicy.SOURCE) 763 public @interface PolicyStatus {} 764 } 765