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 */ getConfig()100 public AudioPolicyConfig getConfig() { return mConfig; } 101 /** @hide */ hasFocusListener()102 public boolean hasFocusListener() { return mFocusListener != null; } 103 /** @hide */ isFocusPolicy()104 public boolean isFocusPolicy() { return mIsFocusPolicy; } 105 /** @hide */ isVolumeController()106 public boolean isVolumeController() { return mVolCb != null; } 107 108 /** 109 * The parameter is guaranteed non-null through the Builder 110 */ AudioPolicy(AudioPolicyConfig config, Context context, Looper looper, AudioPolicyFocusListener fl, AudioPolicyStatusListener sl, boolean isFocusPolicy, AudioPolicyVolumeCallback vc)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 Builder(Context context)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 addMix(@onNull AudioMix mix)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 setLooper(@onNull Looper looper)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 setAudioPolicyFocusListener(AudioPolicyFocusListener l)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 setIsAudioFocusPolicy(boolean isFocusPolicy)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 setAudioPolicyStatusListener(AudioPolicyStatusListener l)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 */ setAudioPolicyVolumeCallback(@onNull AudioPolicyVolumeCallback vc)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 build()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 attachMixes(@onNull List<AudioMix> mixes)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 detachMixes(@onNull List<AudioMix> mixes)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 setRegistration(String regId)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 policyReadyToUse()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 checkMixReadyToUse(AudioMix mix, boolean forTrack)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 getFocusDuckingBehavior()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 setFocusDuckingBehavior(int behavior)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 createAudioRecordSink(AudioMix mix)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 createAudioTrackSource(AudioMix mix)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 getStatus()532 public int getStatus() { 533 return mStatus; 534 } 535 536 @SystemApi 537 public static abstract class AudioPolicyStatusListener { onStatusChange()538 public void onStatusChange() {} onMixStateUpdate(AudioMix mix)539 public void onMixStateUpdate(AudioMix mix) {} 540 } 541 542 @SystemApi 543 public static abstract class AudioPolicyFocusListener { onAudioFocusGrant(AudioFocusInfo afi, int requestResult)544 public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {} onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified)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 */ onAudioFocusRequest(AudioFocusInfo afi, int requestResult)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 */ onAudioFocusAbandon(AudioFocusInfo afi)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 */ AudioPolicyVolumeCallback()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 */ onVolumeAdjustment(@udioManager.VolumeAdjustment int adjustment)580 public void onVolumeAdjustment(@AudioManager.VolumeAdjustment int adjustment) {} 581 } 582 onPolicyStatusChange()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 */ cb()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 { EventHandler(AudioPolicy ap, Looper looper)666 public EventHandler(AudioPolicy ap, Looper looper) { 667 super(looper); 668 } 669 670 @Override handleMessage(Message msg)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 addressForTag(AudioMix mix)722 private static String addressForTag(AudioMix mix) { 723 return "addr=" + mix.getRegistration(); 724 } 725 sendMsg(int msg)726 private void sendMsg(int msg) { 727 if (mEventHandler != null) { 728 mEventHandler.sendEmptyMessage(msg); 729 } 730 } 731 sendMsg(int msg, Object obj, int i)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 getService()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 toLogFriendlyString()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