/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.media.audiopolicy;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.IAudioService;
import android.media.MediaRecorder;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.util.Slog;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;

@hide AudioPolicy provides access to the management of audio routing and audio focus.
/** * @hide * AudioPolicy provides access to the management of audio routing and audio focus. */
@SystemApi public class AudioPolicy { private static final String TAG = "AudioPolicy"; private static final boolean DEBUG = false; private final Object mLock = new Object();
The status of an audio policy that is valid but cannot be used because it is not registered.
/** * The status of an audio policy that is valid but cannot be used because it is not registered. */
@SystemApi public static final int POLICY_STATUS_UNREGISTERED = 1;
The status of an audio policy that is valid, successfully registered and thus active.
/** * The status of an audio policy that is valid, successfully registered and thus active. */
@SystemApi public static final int POLICY_STATUS_REGISTERED = 2; private int mStatus; private String mRegistrationId; private AudioPolicyStatusListener mStatusListener; private boolean mIsFocusPolicy;
The behavior of a policy with regards to audio focus where it relies on the application to do the ducking, the is the legacy and default behavior.
/** * The behavior of a policy with regards to audio focus where it relies on the application * to do the ducking, the is the legacy and default behavior. */
@SystemApi public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; public static final int FOCUS_POLICY_DUCKING_DEFAULT = FOCUS_POLICY_DUCKING_IN_APP;
The behavior of a policy with regards to audio focus where it handles ducking instead of the application losing focus and being signaled it can duck (as communicated by AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK).
Can only be used after having set a listener with setAudioPolicyFocusListener(AudioPolicyFocusListener).
/** * The behavior of a policy with regards to audio focus where it handles ducking instead * of the application losing focus and being signaled it can duck (as communicated by * {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}). * <br>Can only be used after having set a listener with * {@link AudioPolicy#setAudioPolicyFocusListener(AudioPolicyFocusListener)}. */
@SystemApi public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; private AudioPolicyFocusListener mFocusListener; private final AudioPolicyVolumeCallback mVolCb; private Context mContext; private AudioPolicyConfig mConfig;
@hide
/** @hide */
public AudioPolicyConfig getConfig() { return mConfig; }
@hide
/** @hide */
public boolean hasFocusListener() { return mFocusListener != null; }
@hide
/** @hide */
public boolean isFocusPolicy() { return mIsFocusPolicy; }
@hide
/** @hide */
public boolean isVolumeController() { return mVolCb != null; }
The parameter is guaranteed non-null through the Builder
/** * The parameter is guaranteed non-null through the Builder */
private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper, AudioPolicyFocusListener fl, AudioPolicyStatusListener sl, boolean isFocusPolicy, AudioPolicyVolumeCallback vc) { mConfig = config; mStatus = POLICY_STATUS_UNREGISTERED; mContext = context; if (looper == null) { looper = Looper.getMainLooper(); } if (looper != null) { mEventHandler = new EventHandler(this, looper); } else { mEventHandler = null; Log.e(TAG, "No event handler due to looper without a thread"); } mFocusListener = fl; mStatusListener = sl; mIsFocusPolicy = isFocusPolicy; mVolCb = vc; }
Builder class for AudioPolicy objects. By default the policy to be created doesn't govern audio focus decisions.
/** * Builder class for {@link AudioPolicy} objects. * By default the policy to be created doesn't govern audio focus decisions. */
@SystemApi public static class Builder { private ArrayList<AudioMix> mMixes; private Context mContext; private Looper mLooper; private AudioPolicyFocusListener mFocusListener; private AudioPolicyStatusListener mStatusListener; private boolean mIsFocusPolicy = false; private AudioPolicyVolumeCallback mVolCb;
Constructs a new Builder with no audio mixes.
Params:
  • context – the context for the policy
/** * Constructs a new Builder with no audio mixes. * @param context the context for the policy */
@SystemApi public Builder(Context context) { mMixes = new ArrayList<AudioMix>(); mContext = context; }
Add an AudioMix to be part of the audio policy being built.
Params:
  • mix – a non-null AudioMix to be part of the audio policy.
Throws:
Returns:the same Builder instance.
/** * Add an {@link AudioMix} to be part of the audio policy being built. * @param mix a non-null {@link AudioMix} to be part of the audio policy. * @return the same Builder instance. * @throws IllegalArgumentException */
@SystemApi public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException { if (mix == null) { throw new IllegalArgumentException("Illegal null AudioMix argument"); } mMixes.add(mix); return this; }
Sets the Looper on which to run the event loop.
Params:
  • looper – a non-null specific Looper.
Throws:
Returns:the same Builder instance.
/** * Sets the {@link Looper} on which to run the event loop. * @param looper a non-null specific Looper. * @return the same Builder instance. * @throws IllegalArgumentException */
@SystemApi public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException { if (looper == null) { throw new IllegalArgumentException("Illegal null Looper argument"); } mLooper = looper; return this; }
Sets the audio focus listener for the policy.
Params:
/** * Sets the audio focus listener for the policy. * @param l a {@link AudioPolicy.AudioPolicyFocusListener} */
@SystemApi public void setAudioPolicyFocusListener(AudioPolicyFocusListener l) { mFocusListener = l; }
Declares whether this policy will grant and deny audio focus through the AudioPolicyFocusListener. If set to true, it is mandatory to set an AudioPolicyFocusListener in order to successfully build an AudioPolicy instance.
Params:
  • enforce – true if the policy will govern audio focus decisions.
Returns:the same Builder instance.
/** * Declares whether this policy will grant and deny audio focus through * the {@link AudioPolicy.AudioPolicyFocusListener}. * If set to {@code true}, it is mandatory to set an * {@link AudioPolicy.AudioPolicyFocusListener} in order to successfully build * an {@code AudioPolicy} instance. * @param enforce true if the policy will govern audio focus decisions. * @return the same Builder instance. */
@SystemApi public Builder setIsAudioFocusPolicy(boolean isFocusPolicy) { mIsFocusPolicy = isFocusPolicy; return this; }
Sets the audio policy status listener.
Params:
/** * Sets the audio policy status listener. * @param l a {@link AudioPolicy.AudioPolicyStatusListener} */
@SystemApi public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) { mStatusListener = l; } @SystemApi /** * Sets the callback to receive all volume key-related events. * The callback will only be called if the device is configured to handle volume events * in the PhoneWindowManager (see config_handleVolumeKeysInWindowManager) * @param vc * @return the same Builder instance. */ public Builder setAudioPolicyVolumeCallback(@NonNull AudioPolicyVolumeCallback vc) { if (vc == null) { throw new IllegalArgumentException("Invalid null volume callback"); } mVolCb = vc; return this; }
Combines all of the attributes that have been set on this Builder and returns a new AudioPolicy object.
Throws:
Returns:a new AudioPolicy object.
/** * Combines all of the attributes that have been set on this {@code Builder} and returns a * new {@link AudioPolicy} object. * @return a new {@code AudioPolicy} object. * @throws IllegalStateException if there is no * {@link AudioPolicy.AudioPolicyStatusListener} but the policy was configured * as an audio focus policy with {@link #setIsAudioFocusPolicy(boolean)}. */
@SystemApi public AudioPolicy build() { if (mStatusListener != null) { // the AudioPolicy status listener includes updates on each mix activity state for (AudioMix mix : mMixes) { mix.mCallbackFlags |= AudioMix.CALLBACK_FLAG_NOTIFY_ACTIVITY; } } if (mIsFocusPolicy && mFocusListener == null) { throw new IllegalStateException("Cannot be a focus policy without " + "an AudioPolicyFocusListener"); } return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper, mFocusListener, mStatusListener, mIsFocusPolicy, mVolCb); } }
Params:
@hide Update the current configuration of the set of audio mixes by adding new ones, while keeping the policy registered. This method can only be called on a registered policy.
Returns:AudioManager.SUCCESS if the change was successful, AudioManager.ERROR otherwise.
/** * @hide * Update the current configuration of the set of audio mixes by adding new ones, while * keeping the policy registered. * This method can only be called on a registered policy. * @param mixes the list of {@link AudioMix} to add * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR} * otherwise. */
@SystemApi public int attachMixes(@NonNull List<AudioMix> mixes) { if (mixes == null) { throw new IllegalArgumentException("Illegal null list of AudioMix"); } synchronized (mLock) { if (mStatus != POLICY_STATUS_REGISTERED) { throw new IllegalStateException("Cannot alter unregistered AudioPolicy"); } final ArrayList<AudioMix> zeMixes = new ArrayList<AudioMix>(mixes.size()); for (AudioMix mix : mixes) { if (mix == null) { throw new IllegalArgumentException("Illegal null AudioMix in attachMixes"); } else { zeMixes.add(mix); } } final AudioPolicyConfig cfg = new AudioPolicyConfig(zeMixes); IAudioService service = getService(); try { final int status = service.addMixForPolicy(cfg, this.cb()); if (status == AudioManager.SUCCESS) { mConfig.add(zeMixes); } return status; } catch (RemoteException e) { Log.e(TAG, "Dead object in attachMixes", e); return AudioManager.ERROR; } } }
Params:
  • mixes – the list of AudioMix to remove
@hide Update the current configuration of the set of audio mixes by removing some, while keeping the policy registered. This method can only be called on a registered policy.
Returns:AudioManager.SUCCESS if the change was successful, AudioManager.ERROR otherwise.
/** * @hide * Update the current configuration of the set of audio mixes by removing some, while * keeping the policy registered. * This method can only be called on a registered policy. * @param mixes the list of {@link AudioMix} to remove * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR} * otherwise. */
@SystemApi public int detachMixes(@NonNull List<AudioMix> mixes) { if (mixes == null) { throw new IllegalArgumentException("Illegal null list of AudioMix"); } synchronized (mLock) { if (mStatus != POLICY_STATUS_REGISTERED) { throw new IllegalStateException("Cannot alter unregistered AudioPolicy"); } final ArrayList<AudioMix> zeMixes = new ArrayList<AudioMix>(mixes.size()); for (AudioMix mix : mixes) { if (mix == null) { throw new IllegalArgumentException("Illegal null AudioMix in detachMixes"); // TODO also check mix is currently contained in list of mixes } else { zeMixes.add(mix); } } final AudioPolicyConfig cfg = new AudioPolicyConfig(zeMixes); IAudioService service = getService(); try { final int status = service.removeMixForPolicy(cfg, this.cb()); if (status == AudioManager.SUCCESS) { mConfig.remove(zeMixes); } return status; } catch (RemoteException e) { Log.e(TAG, "Dead object in detachMixes", e); return AudioManager.ERROR; } } } public void setRegistration(String regId) { synchronized (mLock) { mRegistrationId = regId; mConfig.setRegistration(regId); if (regId != null) { mStatus = POLICY_STATUS_REGISTERED; } else { mStatus = POLICY_STATUS_UNREGISTERED; } } sendMsg(MSG_POLICY_STATUS_CHANGE); } private boolean policyReadyToUse() { synchronized (mLock) { if (mStatus != POLICY_STATUS_REGISTERED) { Log.e(TAG, "Cannot use unregistered AudioPolicy"); return false; } if (mContext == null) { Log.e(TAG, "Cannot use AudioPolicy without context"); return false; } if (mRegistrationId == null) { Log.e(TAG, "Cannot use unregistered AudioPolicy"); return false; } } if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_AUDIO_ROUTING))) { Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid " + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING"); return false; } return true; } private void checkMixReadyToUse(AudioMix mix, boolean forTrack) throws IllegalArgumentException{ if (mix == null) { String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation" : "Invalid null AudioMix for AudioRecord creation"; throw new IllegalArgumentException(msg); } if (!mConfig.mMixes.contains(mix)) { throw new IllegalArgumentException("Invalid mix: not part of this policy"); } if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK) { throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back"); } if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) { throw new IllegalArgumentException( "Invalid AudioMix: not defined for being a recording source"); } if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) { throw new IllegalArgumentException( "Invalid AudioMix: not defined for capturing playback"); } }
Returns the current behavior for audio focus-related ducking.
Returns:FOCUS_POLICY_DUCKING_IN_APP or FOCUS_POLICY_DUCKING_IN_POLICY
/** * Returns the current behavior for audio focus-related ducking. * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY} */
@SystemApi public int getFocusDuckingBehavior() { return mConfig.mDuckingPolicy; } // Note on implementation: not part of the Builder as there can be only one registered policy // that handles ducking but there can be multiple policies
Sets the behavior for audio focus-related ducking. There must be a focus listener if this policy is to handle ducking.
Params:
Throws:
Returns:AudioManager.SUCCESS or AudioManager.ERROR (for instance if there is already an audio policy that handles ducking).
/** * Sets the behavior for audio focus-related ducking. * There must be a focus listener if this policy is to handle ducking. * @param behavior {@link #FOCUS_POLICY_DUCKING_IN_APP} or * {@link #FOCUS_POLICY_DUCKING_IN_POLICY} * @return {@link AudioManager#SUCCESS} or {@link AudioManager#ERROR} (for instance if there * is already an audio policy that handles ducking). * @throws IllegalArgumentException * @throws IllegalStateException */
@SystemApi public int setFocusDuckingBehavior(int behavior) throws IllegalArgumentException, IllegalStateException { if ((behavior != FOCUS_POLICY_DUCKING_IN_APP) && (behavior != FOCUS_POLICY_DUCKING_IN_POLICY)) { throw new IllegalArgumentException("Invalid ducking behavior " + behavior); } synchronized (mLock) { if (mStatus != POLICY_STATUS_REGISTERED) { throw new IllegalStateException( "Cannot change ducking behavior for unregistered policy"); } if ((behavior == FOCUS_POLICY_DUCKING_IN_POLICY) && (mFocusListener == null)) { // there must be a focus listener if the policy handles ducking throw new IllegalStateException( "Cannot handle ducking without an audio focus listener"); } IAudioService service = getService(); try { final int status = service.setFocusPropertiesForPolicy(behavior /*duckingBehavior*/, this.cb()); if (status == AudioManager.SUCCESS) { mConfig.mDuckingPolicy = behavior; } return status; } catch (RemoteException e) { Log.e(TAG, "Dead object in setFocusPropertiesForPolicy for behavior", e); return AudioManager.ERROR; } } }
Create an AudioRecord instance that is associated with the given AudioMix. Audio buffers recorded through the created instance will contain the mix of the audio streams that fed the given mixer.
Params:
Throws:
Returns:a new AudioRecord instance whose data format is the one defined in the AudioMix, or null if this policy was not successfully registered with AudioManager.registerAudioPolicy(AudioPolicy).
/** * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}. * Audio buffers recorded through the created instance will contain the mix of the audio * streams that fed the given mixer. * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy. * @return a new {@link AudioRecord} instance whose data format is the one defined in the * {@link AudioMix}, or null if this policy was not successfully registered * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. * @throws IllegalArgumentException */
@SystemApi public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException { if (!policyReadyToUse()) { Log.e(TAG, "Cannot create AudioRecord sink for AudioMix"); return null; } checkMixReadyToUse(mix, false/*not for an AudioTrack*/); // create an AudioFormat from the mix format compatible with recording, as the mix // was defined for playback AudioFormat mixFormat = new AudioFormat.Builder(mix.getFormat()) .setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask( mix.getFormat().getChannelMask())) .build(); // create the AudioRecord, configured for loop back, using the same format as the mix AudioRecord ar = new AudioRecord( new AudioAttributes.Builder() .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX) .addTag(addressForTag(mix)) .addTag(AudioRecord.SUBMIX_FIXED_VOLUME) .build(), mixFormat, AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(), // using stereo for buffer size to avoid the current poor support for masks AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()), AudioManager.AUDIO_SESSION_ID_GENERATE ); return ar; }
Create an AudioTrack instance that is associated with the given AudioMix. Audio buffers played through the created instance will be sent to the given mix to be recorded through the recording APIs.
Params:
Throws:
Returns:a new AudioTrack instance whose data format is the one defined in the AudioMix, or null if this policy was not successfully registered with AudioManager.registerAudioPolicy(AudioPolicy).
/** * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}. * Audio buffers played through the created instance will be sent to the given mix * to be recorded through the recording APIs. * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy. * @return a new {@link AudioTrack} instance whose data format is the one defined in the * {@link AudioMix}, or null if this policy was not successfully registered * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. * @throws IllegalArgumentException */
@SystemApi public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException { if (!policyReadyToUse()) { Log.e(TAG, "Cannot create AudioTrack source for AudioMix"); return null; } checkMixReadyToUse(mix, true/*for an AudioTrack*/); // create the AudioTrack, configured for loop back, using the same format as the mix AudioTrack at = new AudioTrack( new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE) .addTag(addressForTag(mix)) .build(), mix.getFormat(), AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(), mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()), AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE ); return at; } @SystemApi public int getStatus() { return mStatus; } @SystemApi public static abstract class AudioPolicyStatusListener { public void onStatusChange() {} public void onMixStateUpdate(AudioMix mix) {} } @SystemApi public static abstract class AudioPolicyFocusListener { public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {} public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {}
Called whenever an application requests audio focus. Only ever called if the AudioPolicy was built with Builder.setIsAudioFocusPolicy(boolean) set to true.
Params:
/** * Called whenever an application requests audio focus. * Only ever called if the {@link AudioPolicy} was built with * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}. * @param afi information about the focus request and the requester * @param requestResult deprecated after the addition of * {@link AudioManager#setFocusRequestResult(AudioFocusInfo, int, AudioPolicy)} * in Android P, always equal to {@link #AUDIOFOCUS_REQUEST_GRANTED}. */
public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {}
Called whenever an application abandons audio focus. Only ever called if the AudioPolicy was built with Builder.setIsAudioFocusPolicy(boolean) set to true.
Params:
  • afi – information about the focus request being abandoned and the original requester.
/** * Called whenever an application abandons audio focus. * Only ever called if the {@link AudioPolicy} was built with * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to {@code true}. * @param afi information about the focus request being abandoned and the original * requester. */
public void onAudioFocusAbandon(AudioFocusInfo afi) {} } @SystemApi /** * Callback class to receive volume change-related events. * See {@link #Builder.setAudioPolicyVolumeCallback(AudioPolicyCallback)} to configure the * {@link AudioPolicy} to receive those events. * */ public static abstract class AudioPolicyVolumeCallback {
@hide
/** @hide */
public AudioPolicyVolumeCallback() {}
Called when volume key-related changes are triggered, on the key down event.
Params:
  • adjustment – the type of volume adjustment for the key.
/** * Called when volume key-related changes are triggered, on the key down event. * @param adjustment the type of volume adjustment for the key. */
public void onVolumeAdjustment(@AudioManager.VolumeAdjustment int adjustment) {} } private void onPolicyStatusChange() { AudioPolicyStatusListener l; synchronized (mLock) { if (mStatusListener == null) { return; } l = mStatusListener; } l.onStatusChange(); } //================================================== // Callback interface
@hide
/** @hide */
public IAudioPolicyCallback cb() { return mPolicyCb; } private final IAudioPolicyCallback mPolicyCb = new IAudioPolicyCallback.Stub() { public void notifyAudioFocusGrant(AudioFocusInfo afi, int requestResult) { sendMsg(MSG_FOCUS_GRANT, afi, requestResult); if (DEBUG) { Log.v(TAG, "notifyAudioFocusGrant: pack=" + afi.getPackageName() + " client=" + afi.getClientId() + "reqRes=" + requestResult); } } public void notifyAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) { sendMsg(MSG_FOCUS_LOSS, afi, wasNotified ? 1 : 0); if (DEBUG) { Log.v(TAG, "notifyAudioFocusLoss: pack=" + afi.getPackageName() + " client=" + afi.getClientId() + "wasNotified=" + wasNotified); } } public void notifyAudioFocusRequest(AudioFocusInfo afi, int requestResult) { sendMsg(MSG_FOCUS_REQUEST, afi, requestResult); if (DEBUG) { Log.v(TAG, "notifyAudioFocusRequest: pack=" + afi.getPackageName() + " client=" + afi.getClientId() + " gen=" + afi.getGen()); } } public void notifyAudioFocusAbandon(AudioFocusInfo afi) { sendMsg(MSG_FOCUS_ABANDON, afi, 0 /* ignored */); if (DEBUG) { Log.v(TAG, "notifyAudioFocusAbandon: pack=" + afi.getPackageName() + " client=" + afi.getClientId()); } } public void notifyMixStateUpdate(String regId, int state) { for (AudioMix mix : mConfig.getMixes()) { if (mix.getRegistration().equals(regId)) { mix.mMixState = state; sendMsg(MSG_MIX_STATE_UPDATE, mix, 0/*ignored*/); if (DEBUG) { Log.v(TAG, "notifyMixStateUpdate: regId=" + regId + " state=" + state); } } } } public void notifyVolumeAdjust(int adjustment) { sendMsg(MSG_VOL_ADJUST, null /* ignored */, adjustment); if (DEBUG) { Log.v(TAG, "notifyVolumeAdjust: " + adjustment); } } }; //================================================== // Event handling private final EventHandler mEventHandler; private final static int MSG_POLICY_STATUS_CHANGE = 0; private final static int MSG_FOCUS_GRANT = 1; private final static int MSG_FOCUS_LOSS = 2; private final static int MSG_MIX_STATE_UPDATE = 3; private final static int MSG_FOCUS_REQUEST = 4; private final static int MSG_FOCUS_ABANDON = 5; private final static int MSG_VOL_ADJUST = 6; private class EventHandler extends Handler { public EventHandler(AudioPolicy ap, Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch(msg.what) { case MSG_POLICY_STATUS_CHANGE: onPolicyStatusChange(); break; case MSG_FOCUS_GRANT: if (mFocusListener != null) { mFocusListener.onAudioFocusGrant( (AudioFocusInfo) msg.obj, msg.arg1); } break; case MSG_FOCUS_LOSS: if (mFocusListener != null) { mFocusListener.onAudioFocusLoss( (AudioFocusInfo) msg.obj, msg.arg1 != 0); } break; case MSG_MIX_STATE_UPDATE: if (mStatusListener != null) { mStatusListener.onMixStateUpdate((AudioMix) msg.obj); } break; case MSG_FOCUS_REQUEST: if (mFocusListener != null) { mFocusListener.onAudioFocusRequest((AudioFocusInfo) msg.obj, msg.arg1); } else { // should never be null, but don't crash Log.e(TAG, "Invalid null focus listener for focus request event"); } break; case MSG_FOCUS_ABANDON: if (mFocusListener != null) { // should never be null mFocusListener.onAudioFocusAbandon((AudioFocusInfo) msg.obj); } else { // should never be null, but don't crash Log.e(TAG, "Invalid null focus listener for focus abandon event"); } break; case MSG_VOL_ADJUST: if (mVolCb != null) { mVolCb.onVolumeAdjustment(msg.arg1); } else { // should never be null, but don't crash Log.e(TAG, "Invalid null volume event"); } break; default: Log.e(TAG, "Unknown event " + msg.what); } } } //========================================================== // Utils private static String addressForTag(AudioMix mix) { return "addr=" + mix.getRegistration(); } private void sendMsg(int msg) { if (mEventHandler != null) { mEventHandler.sendEmptyMessage(msg); } } private void sendMsg(int msg, Object obj, int i) { if (mEventHandler != null) { mEventHandler.sendMessage( mEventHandler.obtainMessage(msg, i /*arg1*/, 0 /*arg2, ignored*/, obj)); } } private static IAudioService sService; private static IAudioService getService() { if (sService != null) { return sService; } IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); sService = IAudioService.Stub.asInterface(b); return sService; } public String toLogFriendlyString() { String textDump = new String("android.media.audiopolicy.AudioPolicy:\n"); textDump += "config=" + mConfig.toLogFriendlyString(); return (textDump); }
@hide
/** @hide */
@IntDef({ POLICY_STATUS_REGISTERED, POLICY_STATUS_UNREGISTERED }) @Retention(RetentionPolicy.SOURCE) public @interface PolicyStatus {} }