Merge "Audio focus: API for external audio focus policy" into oc-dev

This commit is contained in:
TreeHugger Robot
2017-04-04 02:39:58 +00:00
committed by Android (Google) Code Review
8 changed files with 424 additions and 18 deletions

View File

@ -22842,6 +22842,7 @@ package android.media {
method public void adjustStreamVolume(int, int, int); method public void adjustStreamVolume(int, int, int);
method public void adjustSuggestedStreamVolume(int, int, int); method public void adjustSuggestedStreamVolume(int, int, int);
method public void adjustVolume(int, int); method public void adjustVolume(int, int);
method public int dispatchAudioFocusChange(android.media.AudioFocusInfo, int, android.media.audiopolicy.AudioPolicy);
method public void dispatchMediaKeyEvent(android.view.KeyEvent); method public void dispatchMediaKeyEvent(android.view.KeyEvent);
method public int generateAudioSessionId(); method public int generateAudioSessionId();
method public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations(); method public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations();
@ -25844,8 +25845,10 @@ package android.media.audiopolicy {
public static abstract class AudioPolicy.AudioPolicyFocusListener { public static abstract class AudioPolicy.AudioPolicyFocusListener {
ctor public AudioPolicy.AudioPolicyFocusListener(); ctor public AudioPolicy.AudioPolicyFocusListener();
method public void onAudioFocusAbandon(android.media.AudioFocusInfo);
method public void onAudioFocusGrant(android.media.AudioFocusInfo, int); method public void onAudioFocusGrant(android.media.AudioFocusInfo, int);
method public void onAudioFocusLoss(android.media.AudioFocusInfo, boolean); method public void onAudioFocusLoss(android.media.AudioFocusInfo, boolean);
method public void onAudioFocusRequest(android.media.AudioFocusInfo, int);
} }
public static abstract class AudioPolicy.AudioPolicyStatusListener { public static abstract class AudioPolicy.AudioPolicyStatusListener {
@ -25860,6 +25863,7 @@ package android.media.audiopolicy {
method public android.media.audiopolicy.AudioPolicy build(); method public android.media.audiopolicy.AudioPolicy build();
method public void setAudioPolicyFocusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener); method public void setAudioPolicyFocusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener);
method public void setAudioPolicyStatusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener); method public void setAudioPolicyStatusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener);
method public android.media.audiopolicy.AudioPolicy.Builder setIsAudioFocusPolicy(boolean);
method public android.media.audiopolicy.AudioPolicy.Builder setLooper(android.os.Looper) throws java.lang.IllegalArgumentException; method public android.media.audiopolicy.AudioPolicy.Builder setLooper(android.os.Looper) throws java.lang.IllegalArgumentException;
} }

View File

@ -2538,6 +2538,44 @@ public class AudioManager {
} }
} }
/**
* @hide
* Notifies an application with a focus listener of gain or loss of audio focus.
* This method can only be used by owners of an {@link AudioPolicy} configured with
* {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to true.
* @param afi the recipient of the focus change, that has previously requested audio focus, and
* that was received by the {@code AudioPolicy} through
* {@link AudioPolicy.AudioPolicyFocusListener#onAudioFocusRequest(AudioFocusInfo, int)}.
* @param focusChange one of focus gain types ({@link #AUDIOFOCUS_GAIN},
* {@link #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or
* {@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE})
* or one of the focus loss types ({@link AudioManager#AUDIOFOCUS_LOSS},
* {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT},
* or {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}).
* <br>For the focus gain, the change type should be the same as the app requested.
* @param ap a valid registered {@link AudioPolicy} configured as a focus policy.
* @return {@link #AUDIOFOCUS_REQUEST_GRANTED} if the dispatch was successfully sent, or
* {@link #AUDIOFOCUS_REQUEST_FAILED} if the focus client didn't have a listener, or
* if there was an error sending the request.
* @throws NullPointerException if the {@link AudioFocusInfo} or {@link AudioPolicy} are null.
*/
@SystemApi
public int dispatchAudioFocusChange(@NonNull AudioFocusInfo afi, int focusChange,
@NonNull AudioPolicy ap) {
if (afi == null) {
throw new NullPointerException("Illegal null AudioFocusInfo");
}
if (ap == null) {
throw new NullPointerException("Illegal null AudioPolicy");
}
final IAudioService service = getService();
try {
return service.dispatchFocusChange(afi, focusChange, ap.cb());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** /**
* @hide * @hide
* Used internally by telephony package to abandon audio focus, typically after a call or * Used internally by telephony package to abandon audio focus, typically after a call or
@ -2548,7 +2586,7 @@ public class AudioManager {
final IAudioService service = getService(); final IAudioService service = getService();
try { try {
service.abandonAudioFocus(null, AudioSystem.IN_VOICE_COMM_FOCUS_ID, service.abandonAudioFocus(null, AudioSystem.IN_VOICE_COMM_FOCUS_ID,
null /*AudioAttributes, legacy behavior*/); null /*AudioAttributes, legacy behavior*/, getContext().getOpPackageName());
} catch (RemoteException e) { } catch (RemoteException e) {
throw e.rethrowFromSystemServer(); throw e.rethrowFromSystemServer();
} }
@ -2579,7 +2617,7 @@ public class AudioManager {
final IAudioService service = getService(); final IAudioService service = getService();
try { try {
status = service.abandonAudioFocus(mAudioFocusDispatcher, status = service.abandonAudioFocus(mAudioFocusDispatcher,
getIdForAudioFocusListener(l), aa); getIdForAudioFocusListener(l), aa, getContext().getOpPackageName());
} catch (RemoteException e) { } catch (RemoteException e) {
throw e.rethrowFromSystemServer(); throw e.rethrowFromSystemServer();
} }
@ -2787,7 +2825,7 @@ public class AudioManager {
final IAudioService service = getService(); final IAudioService service = getService();
try { try {
String regId = service.registerAudioPolicy(policy.getConfig(), policy.cb(), String regId = service.registerAudioPolicy(policy.getConfig(), policy.cb(),
policy.hasFocusListener()); policy.hasFocusListener(), policy.isFocusPolicy());
if (regId == null) { if (regId == null) {
return ERROR; return ERROR;
} else { } else {

View File

@ -20,6 +20,7 @@ import android.app.PendingIntent;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.content.ComponentName; import android.content.ComponentName;
import android.media.AudioAttributes; import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
import android.media.AudioPlaybackConfiguration; import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration; import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo; import android.media.AudioRoutesInfo;
@ -122,7 +123,8 @@ interface IAudioService {
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags, IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
IAudioPolicyCallback pcb); IAudioPolicyCallback pcb);
int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, in AudioAttributes aa); int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, in AudioAttributes aa,
in String callingPackageName);
void unregisterAudioFocusClient(String clientId); void unregisterAudioFocusClient(String clientId);
@ -164,7 +166,7 @@ interface IAudioService {
boolean isHdmiSystemAudioSupported(); boolean isHdmiSystemAudioSupported();
String registerAudioPolicy(in AudioPolicyConfig policyConfig, String registerAudioPolicy(in AudioPolicyConfig policyConfig,
in IAudioPolicyCallback pcb, boolean hasFocusListener); in IAudioPolicyCallback pcb, boolean hasFocusListener, boolean isFocusPolicy);
oneway void unregisterAudioPolicyAsync(in IAudioPolicyCallback pcb); oneway void unregisterAudioPolicyAsync(in IAudioPolicyCallback pcb);
@ -196,5 +198,8 @@ interface IAudioService {
int getFocusRampTimeMs(in int focusGain, in AudioAttributes attr); int getFocusRampTimeMs(in int focusGain, in AudioAttributes attr);
int dispatchFocusChange(in AudioFocusInfo afi, in int focusChange,
in IAudioPolicyCallback pcb);
// WARNING: read warning at top of file, it is recommended to add new methods at the end // WARNING: read warning at top of file, it is recommended to add new methods at the end
} }

View File

@ -68,6 +68,7 @@ public class AudioPolicy {
private int mStatus; private int mStatus;
private String mRegistrationId; private String mRegistrationId;
private AudioPolicyStatusListener mStatusListener; private AudioPolicyStatusListener mStatusListener;
private boolean mIsFocusPolicy;
/** /**
* The behavior of a policy with regards to audio focus where it relies on the application * The behavior of a policy with regards to audio focus where it relies on the application
@ -96,12 +97,14 @@ public class AudioPolicy {
public AudioPolicyConfig getConfig() { return mConfig; } public AudioPolicyConfig getConfig() { return mConfig; }
/** @hide */ /** @hide */
public boolean hasFocusListener() { return mFocusListener != null; } public boolean hasFocusListener() { return mFocusListener != null; }
/** @hide */
public boolean isFocusPolicy() { return mIsFocusPolicy; }
/** /**
* 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, private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper,
AudioPolicyFocusListener fl, AudioPolicyStatusListener sl) { AudioPolicyFocusListener fl, AudioPolicyStatusListener sl, boolean isFocusPolicy) {
mConfig = config; mConfig = config;
mStatus = POLICY_STATUS_UNREGISTERED; mStatus = POLICY_STATUS_UNREGISTERED;
mContext = context; mContext = context;
@ -116,10 +119,12 @@ public class AudioPolicy {
} }
mFocusListener = fl; mFocusListener = fl;
mStatusListener = sl; mStatusListener = sl;
mIsFocusPolicy = isFocusPolicy;
} }
/** /**
* Builder class for {@link AudioPolicy} objects * Builder class for {@link AudioPolicy} objects.
* By default the policy to be created doesn't govern audio focus decisions.
*/ */
@SystemApi @SystemApi
public static class Builder { public static class Builder {
@ -128,6 +133,7 @@ public class AudioPolicy {
private Looper mLooper; private Looper mLooper;
private AudioPolicyFocusListener mFocusListener; private AudioPolicyFocusListener mFocusListener;
private AudioPolicyStatusListener mStatusListener; private AudioPolicyStatusListener mStatusListener;
private boolean mIsFocusPolicy = false;
/** /**
* Constructs a new Builder with no audio mixes. * Constructs a new Builder with no audio mixes.
@ -178,6 +184,21 @@ public class AudioPolicy {
mFocusListener = l; mFocusListener = l;
} }
/**
* Declares whether this policy will grant and deny audio focus through
* the {@link AudioPolicy.AudioPolicyStatusListener}.
* If set to {@code true}, it is mandatory to set an
* {@link AudioPolicy.AudioPolicyStatusListener} 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. * Sets the audio policy status listener.
* @param l a {@link AudioPolicy.AudioPolicyStatusListener} * @param l a {@link AudioPolicy.AudioPolicyStatusListener}
@ -187,6 +208,14 @@ public class AudioPolicy {
mStatusListener = l; mStatusListener = l;
} }
/**
* 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 @SystemApi
public AudioPolicy build() { public AudioPolicy build() {
if (mStatusListener != null) { if (mStatusListener != null) {
@ -195,8 +224,12 @@ public class AudioPolicy {
mix.mCallbackFlags |= AudioMix.CALLBACK_FLAG_NOTIFY_ACTIVITY; 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, return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper,
mFocusListener, mStatusListener); mFocusListener, mStatusListener, mIsFocusPolicy);
} }
} }
@ -402,6 +435,24 @@ public class AudioPolicy {
public static abstract class AudioPolicyFocusListener { public static abstract class AudioPolicyFocusListener {
public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {} public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {}
public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {} public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {}
/**
* 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 the result that was returned synchronously by the framework to the
* application, {@link #AUDIOFOCUS_REQUEST_FAILED},or
* {@link #AUDIOFOCUS_REQUEST_DELAYED}.
*/
public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {}
/**
* 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) {}
} }
private void onPolicyStatusChange() { private void onPolicyStatusChange() {
@ -439,6 +490,22 @@ public class AudioPolicy {
} }
} }
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() + "reqRes=" + requestResult);
}
}
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) { public void notifyMixStateUpdate(String regId, int state) {
for (AudioMix mix : mConfig.getMixes()) { for (AudioMix mix : mConfig.getMixes()) {
if (mix.getRegistration().equals(regId)) { if (mix.getRegistration().equals(regId)) {
@ -459,6 +526,8 @@ public class AudioPolicy {
private final static int MSG_FOCUS_GRANT = 1; private final static int MSG_FOCUS_GRANT = 1;
private final static int MSG_FOCUS_LOSS = 2; private final static int MSG_FOCUS_LOSS = 2;
private final static int MSG_MIX_STATE_UPDATE = 3; 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 class EventHandler extends Handler { private class EventHandler extends Handler {
public EventHandler(AudioPolicy ap, Looper looper) { public EventHandler(AudioPolicy ap, Looper looper) {
@ -488,6 +557,20 @@ public class AudioPolicy {
mStatusListener.onMixStateUpdate((AudioMix) msg.obj); mStatusListener.onMixStateUpdate((AudioMix) msg.obj);
} }
break; 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;
default: default:
Log.e(TAG, "Unknown event " + msg.what); Log.e(TAG, "Unknown event " + msg.what);
} }

View File

@ -22,9 +22,12 @@ import android.media.AudioFocusInfo;
*/ */
oneway interface IAudioPolicyCallback { oneway interface IAudioPolicyCallback {
// callbacks for audio focus // callbacks for audio focus listening
void notifyAudioFocusGrant(in AudioFocusInfo afi, int requestResult); void notifyAudioFocusGrant(in AudioFocusInfo afi, int requestResult);
void notifyAudioFocusLoss(in AudioFocusInfo afi, boolean wasNotified); void notifyAudioFocusLoss(in AudioFocusInfo afi, boolean wasNotified);
// callback for audio focus policy
void notifyAudioFocusRequest(in AudioFocusInfo afi, int requestResult);
void notifyAudioFocusAbandon(in AudioFocusInfo afi);
// callback for mix activity status update // callback for mix activity status update
void notifyMixStateUpdate(in String regId, int state); void notifyMixStateUpdate(in String regId, int state);

View File

@ -55,6 +55,7 @@ import android.hardware.hdmi.HdmiTvClient;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbManager;
import android.media.AudioAttributes; import android.media.AudioAttributes;
import android.media.AudioDevicePort; import android.media.AudioDevicePort;
import android.media.AudioFocusInfo;
import android.media.AudioSystem; import android.media.AudioSystem;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.AudioManager; import android.media.AudioManager;
@ -5634,8 +5635,9 @@ public class AudioService extends IAudioService.Stub
clientId, callingPackageName, flags); clientId, callingPackageName, flags);
} }
public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa) { public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa,
return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa); String callingPackageName) {
return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName);
} }
public void unregisterAudioFocusClient(String clientId) { public void unregisterAudioFocusClient(String clientId) {
@ -5650,6 +5652,7 @@ public class AudioService extends IAudioService.Stub
return mMediaFocusControl.getFocusRampTimeMs(focusGain, attr); return mMediaFocusControl.getFocusRampTimeMs(focusGain, attr);
} }
//==========================================================================================
private boolean readCameraSoundForced() { private boolean readCameraSoundForced() {
return SystemProperties.getBoolean("audio.camerasound.force", false) || return SystemProperties.getBoolean("audio.camerasound.force", false) ||
mContext.getResources().getBoolean( mContext.getResources().getBoolean(
@ -6430,7 +6433,7 @@ public class AudioService extends IAudioService.Stub
// Audio policy management // Audio policy management
//========================================================================================== //==========================================================================================
public String registerAudioPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb, public String registerAudioPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb,
boolean hasFocusListener) { boolean hasFocusListener, boolean isFocusPolicy) {
AudioSystem.setDynamicPolicyCallback(mDynPolicyCallback); AudioSystem.setDynamicPolicyCallback(mDynPolicyCallback);
if (DEBUG_AP) Log.d(TAG, "registerAudioPolicy for " + pcb.asBinder() if (DEBUG_AP) Log.d(TAG, "registerAudioPolicy for " + pcb.asBinder()
@ -6452,7 +6455,8 @@ public class AudioService extends IAudioService.Stub
Slog.e(TAG, "Cannot re-register policy"); Slog.e(TAG, "Cannot re-register policy");
return null; return null;
} }
AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener); AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener,
isFocusPolicy);
pcb.asBinder().linkToDeath(app, 0/*flags*/); pcb.asBinder().linkToDeath(app, 0/*flags*/);
regId = app.getRegistrationId(); regId = app.getRegistrationId();
mAudioPolicies.put(pcb.asBinder(), app); mAudioPolicies.put(pcb.asBinder(), app);
@ -6650,15 +6654,21 @@ public class AudioService extends IAudioService.Stub
* is handling ducking for audio focus. * is handling ducking for audio focus.
*/ */
int mFocusDuckBehavior = AudioPolicy.FOCUS_POLICY_DUCKING_DEFAULT; int mFocusDuckBehavior = AudioPolicy.FOCUS_POLICY_DUCKING_DEFAULT;
boolean mIsFocusPolicy = false;
AudioPolicyProxy(AudioPolicyConfig config, IAudioPolicyCallback token, AudioPolicyProxy(AudioPolicyConfig config, IAudioPolicyCallback token,
boolean hasFocusListener) { boolean hasFocusListener, boolean isFocusPolicy) {
super(config); super(config);
setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++)); setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++));
mPolicyCallback = token; mPolicyCallback = token;
mHasFocusListener = hasFocusListener; mHasFocusListener = hasFocusListener;
if (mHasFocusListener) { if (mHasFocusListener) {
mMediaFocusControl.addFocusFollower(mPolicyCallback); mMediaFocusControl.addFocusFollower(mPolicyCallback);
// can only ever be true if there is a focus listener
if (isFocusPolicy) {
mIsFocusPolicy = true;
mMediaFocusControl.setFocusPolicy(mPolicyCallback);
}
} }
connectMixes(); connectMixes();
} }
@ -6676,6 +6686,9 @@ public class AudioService extends IAudioService.Stub
} }
void release() { void release() {
if (mIsFocusPolicy) {
mMediaFocusControl.unsetFocusPolicy(mPolicyCallback);
}
if (mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) { if (mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) {
mMediaFocusControl.setDuckingInExtPolicyAvailable(false); mMediaFocusControl.setDuckingInExtPolicyAvailable(false);
} }
@ -6690,6 +6703,22 @@ public class AudioService extends IAudioService.Stub
} }
}; };
//======================
// Audio policy: focus
//======================
/** */
public int dispatchFocusChange(AudioFocusInfo afi, int focusChange, IAudioPolicyCallback pcb) {
synchronized (mAudioPolicies) {
if (!mAudioPolicies.containsKey(pcb.asBinder())) {
throw new IllegalStateException("Unregistered AudioPolicy for focus dispatch");
}
return mMediaFocusControl.dispatchFocusChange(afi, focusChange);
}
}
//======================
// misc
//======================
private HashMap<IBinder, AudioPolicyProxy> mAudioPolicies = private HashMap<IBinder, AudioPolicyProxy> mAudioPolicies =
new HashMap<IBinder, AudioPolicyProxy>(); new HashMap<IBinder, AudioPolicyProxy>();
private int mAudioPolicyCounter = 0; // always accessed synchronized on mAudioPolicies private int mAudioPolicyCounter = 0; // always accessed synchronized on mAudioPolicies

View File

@ -33,7 +33,7 @@ import java.io.PrintWriter;
* @hide * @hide
* Class to handle all the information about a user of audio focus. The lifecycle of each * Class to handle all the information about a user of audio focus. The lifecycle of each
* instance is managed by android.media.MediaFocusControl, from its addition to the audio focus * instance is managed by android.media.MediaFocusControl, from its addition to the audio focus
* stack to its release. * stack, or the map of focus owners for an external focus policy, to its release.
*/ */
public class FocusRequester { public class FocusRequester {
@ -101,6 +101,21 @@ public class FocusRequester {
mFocusController = ctlr; mFocusController = ctlr;
} }
FocusRequester(AudioFocusInfo afi, IAudioFocusDispatcher afl,
IBinder source, AudioFocusDeathHandler hdlr, @NonNull MediaFocusControl ctlr) {
mAttributes = afi.getAttributes();
mClientId = afi.getClientId();
mPackageName = afi.getPackageName();
mCallingUid = afi.getClientUid();
mFocusGainRequest = afi.getGainRequest();
mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
mGrantFlags = afi.getFlags();
mFocusDispatcher = afl;
mSourceRef = source;
mDeathHandler = hdlr;
mFocusController = ctlr;
}
boolean hasSameClient(String otherClient) { boolean hasSameClient(String otherClient) {
try { try {
@ -118,6 +133,10 @@ public class FocusRequester {
return (mSourceRef != null) && mSourceRef.equals(ib); return (mSourceRef != null) && mSourceRef.equals(ib);
} }
boolean hasSameDispatcher(IAudioFocusDispatcher fd) {
return (mFocusDispatcher != null) && mFocusDispatcher.equals(fd);
}
boolean hasSamePackage(String pack) { boolean hasSamePackage(String pack) {
try { try {
return mPackageName.compareTo(pack) == 0; return mPackageName.compareTo(pack) == 0;
@ -369,6 +388,35 @@ public class FocusRequester {
} }
} }
int dispatchFocusChange(int focusChange) {
if (mFocusDispatcher == null) {
if (MediaFocusControl.DEBUG) { Log.v(TAG, "dispatchFocusChange: no focus dispatcher"); }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
if (focusChange == AudioManager.AUDIOFOCUS_NONE) {
if (MediaFocusControl.DEBUG) { Log.v(TAG, "dispatchFocusChange: AUDIOFOCUS_NONE"); }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
} else if ((focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
|| focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
|| focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
|| focusChange == AudioManager.AUDIOFOCUS_GAIN)
&& (mFocusGainRequest != focusChange)){
Log.w(TAG, "focus gain was requested with " + mFocusGainRequest
+ ", dispatching " + focusChange);
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
|| focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
|| focusChange == AudioManager.AUDIOFOCUS_LOSS) {
mFocusLossReceived = focusChange;
}
try {
mFocusDispatcher.dispatchAudioFocusChange(focusChange, mClientId);
} catch (android.os.RemoteException e) {
Log.v(TAG, "dispatchFocusChange: error talking to focus listener", e);
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
AudioFocusInfo toAudioFocusInfo() { AudioFocusInfo toAudioFocusInfo() {
return new AudioFocusInfo(mAttributes, mCallingUid, mClientId, mPackageName, return new AudioFocusInfo(mAttributes, mCallingUid, mClientId, mPackageName,
mFocusGainRequest, mFocusLossReceived, mGrantFlags); mFocusGainRequest, mFocusLossReceived, mGrantFlags);

View File

@ -16,6 +16,7 @@
package com.android.server.audio; package com.android.server.audio;
import android.annotation.NonNull;
import android.app.AppOpsManager; import android.app.AppOpsManager;
import android.content.Context; import android.content.Context;
import android.media.AudioAttributes; import android.media.AudioAttributes;
@ -23,6 +24,7 @@ import android.media.AudioFocusInfo;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.AudioSystem; import android.media.AudioSystem;
import android.media.IAudioFocusDispatcher; import android.media.IAudioFocusDispatcher;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.IAudioPolicyCallback; import android.media.audiopolicy.IAudioPolicyCallback;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
@ -32,7 +34,10 @@ import android.util.Log;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack; import java.util.Stack;
import java.text.DateFormat; import java.text.DateFormat;
@ -43,6 +48,7 @@ import java.text.DateFormat;
public class MediaFocusControl implements PlayerFocusEnforcer { public class MediaFocusControl implements PlayerFocusEnforcer {
private static final String TAG = "MediaFocusControl"; private static final String TAG = "MediaFocusControl";
static final boolean DEBUG = false;
/** /**
* set to true so the framework enforces ducking itself, without communicating to apps * set to true so the framework enforces ducking itself, without communicating to apps
@ -155,6 +161,13 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
while(stackIterator.hasNext()) { while(stackIterator.hasNext()) {
stackIterator.next().dump(pw); stackIterator.next().dump(pw);
} }
pw.println("\n");
if (mFocusPolicy == null) {
pw.println("No external focus policy\n");
} else {
pw.println("External focus policy: "+ mFocusPolicy + ", focus owners:\n");
dumpExtFocusPolicyFocusOwners(pw);
}
} }
pw.println("\n"); pw.println("\n");
pw.println(" Notify on duck: " + mNotifyFocusOwnerOnDuck + "\n"); pw.println(" Notify on duck: " + mNotifyFocusOwnerOnDuck + "\n");
@ -233,6 +246,31 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
} }
} }
/**
* Helper function for external focus policy:
* Called synchronized on mAudioFocusLock
* Remove focus listeners from the list of potential focus owners for a particular client when
* it has died.
*/
private void removeFocusEntryForExtPolicy(IBinder cb) {
if (mFocusOwnersForFocusPolicy.isEmpty()) {
return;
}
boolean released = false;
final Set<Entry<String, FocusRequester>> owners = mFocusOwnersForFocusPolicy.entrySet();
final Iterator<Entry<String, FocusRequester>> ownerIterator = owners.iterator();
while (ownerIterator.hasNext()) {
final Entry<String, FocusRequester> owner = ownerIterator.next();
final FocusRequester fr = owner.getValue();
if (fr.hasSameBinder(cb)) {
ownerIterator.remove();
fr.release();
notifyExtFocusPolicyFocusAbandon_syncAf(fr.toAudioFocusInfo());
break;
}
}
}
/** /**
* Helper function: * Helper function:
* Returns true if the system is in a state where the focus can be reevaluated, false otherwise. * Returns true if the system is in a state where the focus can be reevaluated, false otherwise.
@ -297,10 +335,14 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
public void binderDied() { public void binderDied() {
synchronized(mAudioFocusLock) { synchronized(mAudioFocusLock) {
if (mFocusPolicy != null) {
removeFocusEntryForExtPolicy(mCb);
} else {
removeFocusStackEntryOnDeath(mCb); removeFocusStackEntryOnDeath(mCb);
} }
} }
} }
}
/** /**
* Indicates whether to notify an audio focus owner when it loses focus * Indicates whether to notify an audio focus owner when it loses focus
@ -353,6 +395,34 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
} }
} }
private IAudioPolicyCallback mFocusPolicy = null;
// Since we don't have a stack of focus owners when using an external focus policy, we keep
// track of all the focus requesters in this map, with their clientId as the key. This is
// used both for focus dispatch and death handling
private HashMap<String, FocusRequester> mFocusOwnersForFocusPolicy =
new HashMap<String, FocusRequester>();
void setFocusPolicy(IAudioPolicyCallback policy) {
if (policy == null) {
return;
}
synchronized (mAudioFocusLock) {
mFocusPolicy = policy;
}
}
void unsetFocusPolicy(IAudioPolicyCallback policy) {
if (policy == null) {
return;
}
synchronized (mAudioFocusLock) {
if (mFocusPolicy == policy) {
mFocusPolicy = null;
}
}
}
/** /**
* @param pcb non null * @param pcb non null
*/ */
@ -409,6 +479,100 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
} }
} }
/**
* Called synchronized on mAudioFocusLock
* @param afi
* @param requestResult
* @return true if the external audio focus policy (if any) is handling the focus request
*/
boolean notifyExtFocusPolicyFocusRequest_syncAf(AudioFocusInfo afi, int requestResult,
IAudioFocusDispatcher fd, IBinder cb) {
if (mFocusPolicy == null) {
return false;
}
if (DEBUG) {
Log.v(TAG, "notifyExtFocusPolicyFocusRequest client="+afi.getClientId()
+ " dispatcher=" + fd);
}
final FocusRequester existingFr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
if (existingFr != null) {
if (!existingFr.hasSameDispatcher(fd)) {
existingFr.release();
final AudioFocusDeathHandler hdlr = new AudioFocusDeathHandler(cb);
mFocusOwnersForFocusPolicy.put(afi.getClientId(),
new FocusRequester(afi, fd, cb, hdlr, this));
}
} else if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
|| requestResult == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
// new focus (future) focus owner to keep track of
final AudioFocusDeathHandler hdlr = new AudioFocusDeathHandler(cb);
mFocusOwnersForFocusPolicy.put(afi.getClientId(),
new FocusRequester(afi, fd, cb, hdlr, this));
}
try {
//oneway
mFocusPolicy.notifyAudioFocusRequest(afi, requestResult);
} catch (RemoteException e) {
Log.e(TAG, "Can't call notifyAudioFocusRequest() on IAudioPolicyCallback "
+ mFocusPolicy.asBinder(), e);
}
return true;
}
/**
* Called synchronized on mAudioFocusLock
* @param afi
* @param requestResult
* @return true if the external audio focus policy (if any) is handling the focus request
*/
boolean notifyExtFocusPolicyFocusAbandon_syncAf(AudioFocusInfo afi) {
if (mFocusPolicy == null) {
return false;
}
final FocusRequester fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
if (fr != null) {
fr.release();
}
try {
//oneway
mFocusPolicy.notifyAudioFocusAbandon(afi);
} catch (RemoteException e) {
Log.e(TAG, "Can't call notifyAudioFocusAbandon() on IAudioPolicyCallback "
+ mFocusPolicy.asBinder(), e);
}
return true;
}
/** see AudioManager.dispatchFocusChange(AudioFocusInfo afi, int focusChange, AudioPolicy ap) */
int dispatchFocusChange(AudioFocusInfo afi, int focusChange) {
if (DEBUG) {
Log.v(TAG, "dispatchFocusChange " + focusChange + " to afi client="
+ afi.getClientId());
}
synchronized (mAudioFocusLock) {
if (mFocusPolicy == null) {
if (DEBUG) { Log.v(TAG, "> failed: no focus policy" ); }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
final FocusRequester fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
if (fr == null) {
if (DEBUG) { Log.v(TAG, "> failed: no such focus requester known" ); }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
return fr.dispatchFocusChange(focusChange);
}
}
private void dumpExtFocusPolicyFocusOwners(PrintWriter pw) {
final Set<Entry<String, FocusRequester>> owners = mFocusOwnersForFocusPolicy.entrySet();
final Iterator<Entry<String, FocusRequester>> ownerIterator = owners.iterator();
while (ownerIterator.hasNext()) {
final Entry<String, FocusRequester> owner = ownerIterator.next();
final FocusRequester fr = owner.getValue();
fr.dump(pw);
}
}
protected int getCurrentAudioFocus() { protected int getCurrentAudioFocus() {
synchronized(mAudioFocusLock) { synchronized(mAudioFocusLock) {
if (mFocusStack.empty()) { if (mFocusStack.empty()) {
@ -487,10 +651,23 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
& (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0); & (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
if (enteringRingOrCall) { mRingOrCallActive = true; } if (enteringRingOrCall) { mRingOrCallActive = true; }
final AudioFocusInfo afiForExtPolicy;
if (mFocusPolicy != null) {
// construct AudioFocusInfo as it will be communicated to audio focus policy
afiForExtPolicy = new AudioFocusInfo(aa, Binder.getCallingUid(),
clientId, callingPackageName, focusChangeHint, 0 /*lossReceived*/,
flags);
} else {
afiForExtPolicy = null;
}
// handle delayed focus
boolean focusGrantDelayed = false; boolean focusGrantDelayed = false;
if (!canReassignAudioFocus()) { if (!canReassignAudioFocus()) {
if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) { if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED; final int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, result, fd, cb);
return result;
} else { } else {
// request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
// granted right now, so the requester will be inserted in the focus stack // granted right now, so the requester will be inserted in the focus stack
@ -499,6 +676,14 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
} }
} }
// external focus policy: delay request for focus gain?
final int resultWithExtPolicy = AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
if (notifyExtFocusPolicyFocusRequest_syncAf(
afiForExtPolicy, resultWithExtPolicy, fd, cb)) {
// stop handling focus request here as it is handled by external audio focus policy
return resultWithExtPolicy;
}
// handle the potential premature death of the new holder of the focus // handle the potential premature death of the new holder of the focus
// (premature death == death before abandoning focus) // (premature death == death before abandoning focus)
// Register for client death notification // Register for client death notification
@ -569,7 +754,8 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
/** /**
* @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes) * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes)
* */ * */
protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) { protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa,
String callingPackageName) {
// AudioAttributes are currently ignored, to be used for zones // AudioAttributes are currently ignored, to be used for zones
Log.i(TAG, " AudioFocus abandonAudioFocus() from uid/pid " + Binder.getCallingUid() Log.i(TAG, " AudioFocus abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
+ "/" + Binder.getCallingPid() + "/" + Binder.getCallingPid()
@ -577,6 +763,16 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
try { try {
// this will take care of notifying the new focus owner if needed // this will take care of notifying the new focus owner if needed
synchronized(mAudioFocusLock) { synchronized(mAudioFocusLock) {
// external focus policy?
if (mFocusPolicy != null) {
final AudioFocusInfo afi = new AudioFocusInfo(aa, Binder.getCallingUid(),
clientId, callingPackageName, 0 /*gainRequest*/, 0 /*lossReceived*/,
0 /*flags*/);
if (notifyExtFocusPolicyFocusAbandon_syncAf(afi)) {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
}
boolean exitingRingOrCall = mRingOrCallActive boolean exitingRingOrCall = mRingOrCallActive
& (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0); & (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
if (exitingRingOrCall) { mRingOrCallActive = false; } if (exitingRingOrCall) { mRingOrCallActive = false; }