Audio focus: API for external audio focus policy
System API for an external audio focus policy. Extends the system focus listener with interception of focus requests and abandons. Adds method for the focus policy to dispatch focus grants and losses. Test: gts-tradefed run gts -m GtsGmscoreHostTestCases -t 'com.google.android.gts.audio.AudioHostTest#testFocusPolicy' Bug: 30258418 Change-Id: If408569a2dce07a774e0e2f1be9f1af8e426d2d3
This commit is contained in:
@ -22852,6 +22852,7 @@ package android.media {
|
||||
method public void adjustStreamVolume(int, int, int);
|
||||
method public void adjustSuggestedStreamVolume(int, 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 int generateAudioSessionId();
|
||||
method public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations();
|
||||
@ -25853,8 +25854,10 @@ package android.media.audiopolicy {
|
||||
|
||||
public static abstract class AudioPolicy.AudioPolicyFocusListener {
|
||||
ctor public AudioPolicy.AudioPolicyFocusListener();
|
||||
method public void onAudioFocusAbandon(android.media.AudioFocusInfo);
|
||||
method public void onAudioFocusGrant(android.media.AudioFocusInfo, int);
|
||||
method public void onAudioFocusLoss(android.media.AudioFocusInfo, boolean);
|
||||
method public void onAudioFocusRequest(android.media.AudioFocusInfo, int);
|
||||
}
|
||||
|
||||
public static abstract class AudioPolicy.AudioPolicyStatusListener {
|
||||
@ -25869,6 +25872,7 @@ package android.media.audiopolicy {
|
||||
method public android.media.audiopolicy.AudioPolicy build();
|
||||
method public void setAudioPolicyFocusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
* 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();
|
||||
try {
|
||||
service.abandonAudioFocus(null, AudioSystem.IN_VOICE_COMM_FOCUS_ID,
|
||||
null /*AudioAttributes, legacy behavior*/);
|
||||
null /*AudioAttributes, legacy behavior*/, getContext().getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
@ -2579,7 +2617,7 @@ public class AudioManager {
|
||||
final IAudioService service = getService();
|
||||
try {
|
||||
status = service.abandonAudioFocus(mAudioFocusDispatcher,
|
||||
getIdForAudioFocusListener(l), aa);
|
||||
getIdForAudioFocusListener(l), aa, getContext().getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
@ -2787,7 +2825,7 @@ public class AudioManager {
|
||||
final IAudioService service = getService();
|
||||
try {
|
||||
String regId = service.registerAudioPolicy(policy.getConfig(), policy.cb(),
|
||||
policy.hasFocusListener());
|
||||
policy.hasFocusListener(), policy.isFocusPolicy());
|
||||
if (regId == null) {
|
||||
return ERROR;
|
||||
} else {
|
||||
|
@ -20,6 +20,7 @@ import android.app.PendingIntent;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.ComponentName;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioFocusInfo;
|
||||
import android.media.AudioPlaybackConfiguration;
|
||||
import android.media.AudioRecordingConfiguration;
|
||||
import android.media.AudioRoutesInfo;
|
||||
@ -122,7 +123,8 @@ interface IAudioService {
|
||||
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
|
||||
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);
|
||||
|
||||
@ -164,7 +166,7 @@ interface IAudioService {
|
||||
boolean isHdmiSystemAudioSupported();
|
||||
|
||||
String registerAudioPolicy(in AudioPolicyConfig policyConfig,
|
||||
in IAudioPolicyCallback pcb, boolean hasFocusListener);
|
||||
in IAudioPolicyCallback pcb, boolean hasFocusListener, boolean isFocusPolicy);
|
||||
|
||||
oneway void unregisterAudioPolicyAsync(in IAudioPolicyCallback pcb);
|
||||
|
||||
@ -196,5 +198,8 @@ interface IAudioService {
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ public class AudioPolicy {
|
||||
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
|
||||
@ -96,12 +97,14 @@ public class AudioPolicy {
|
||||
public AudioPolicyConfig getConfig() { return mConfig; }
|
||||
/** @hide */
|
||||
public boolean hasFocusListener() { return mFocusListener != null; }
|
||||
/** @hide */
|
||||
public boolean isFocusPolicy() { return mIsFocusPolicy; }
|
||||
|
||||
/**
|
||||
* The parameter is guaranteed non-null through the Builder
|
||||
*/
|
||||
private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper,
|
||||
AudioPolicyFocusListener fl, AudioPolicyStatusListener sl) {
|
||||
AudioPolicyFocusListener fl, AudioPolicyStatusListener sl, boolean isFocusPolicy) {
|
||||
mConfig = config;
|
||||
mStatus = POLICY_STATUS_UNREGISTERED;
|
||||
mContext = context;
|
||||
@ -116,10 +119,12 @@ public class AudioPolicy {
|
||||
}
|
||||
mFocusListener = fl;
|
||||
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
|
||||
public static class Builder {
|
||||
@ -128,6 +133,7 @@ public class AudioPolicy {
|
||||
private Looper mLooper;
|
||||
private AudioPolicyFocusListener mFocusListener;
|
||||
private AudioPolicyStatusListener mStatusListener;
|
||||
private boolean mIsFocusPolicy = false;
|
||||
|
||||
/**
|
||||
* Constructs a new Builder with no audio mixes.
|
||||
@ -178,6 +184,21 @@ public class AudioPolicy {
|
||||
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.
|
||||
* @param l a {@link AudioPolicy.AudioPolicyStatusListener}
|
||||
@ -187,6 +208,14 @@ public class AudioPolicy {
|
||||
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
|
||||
public AudioPolicy build() {
|
||||
if (mStatusListener != null) {
|
||||
@ -195,8 +224,12 @@ public class AudioPolicy {
|
||||
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);
|
||||
mFocusListener, mStatusListener, mIsFocusPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
@ -402,6 +435,24 @@ public class AudioPolicy {
|
||||
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 {@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() {
|
||||
@ -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) {
|
||||
for (AudioMix mix : mConfig.getMixes()) {
|
||||
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_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 class EventHandler extends Handler {
|
||||
public EventHandler(AudioPolicy ap, Looper looper) {
|
||||
@ -488,6 +557,20 @@ public class AudioPolicy {
|
||||
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;
|
||||
default:
|
||||
Log.e(TAG, "Unknown event " + msg.what);
|
||||
}
|
||||
|
@ -22,9 +22,12 @@ import android.media.AudioFocusInfo;
|
||||
*/
|
||||
oneway interface IAudioPolicyCallback {
|
||||
|
||||
// callbacks for audio focus
|
||||
// callbacks for audio focus listening
|
||||
void notifyAudioFocusGrant(in AudioFocusInfo afi, int requestResult);
|
||||
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
|
||||
void notifyMixStateUpdate(in String regId, int state);
|
||||
|
@ -55,6 +55,7 @@ import android.hardware.hdmi.HdmiTvClient;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioDevicePort;
|
||||
import android.media.AudioFocusInfo;
|
||||
import android.media.AudioSystem;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
@ -5633,8 +5634,9 @@ public class AudioService extends IAudioService.Stub
|
||||
clientId, callingPackageName, flags);
|
||||
}
|
||||
|
||||
public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa) {
|
||||
return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa);
|
||||
public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa,
|
||||
String callingPackageName) {
|
||||
return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName);
|
||||
}
|
||||
|
||||
public void unregisterAudioFocusClient(String clientId) {
|
||||
@ -5649,6 +5651,7 @@ public class AudioService extends IAudioService.Stub
|
||||
return mMediaFocusControl.getFocusRampTimeMs(focusGain, attr);
|
||||
}
|
||||
|
||||
//==========================================================================================
|
||||
private boolean readCameraSoundForced() {
|
||||
return SystemProperties.getBoolean("audio.camerasound.force", false) ||
|
||||
mContext.getResources().getBoolean(
|
||||
@ -6429,7 +6432,7 @@ public class AudioService extends IAudioService.Stub
|
||||
// Audio policy management
|
||||
//==========================================================================================
|
||||
public String registerAudioPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb,
|
||||
boolean hasFocusListener) {
|
||||
boolean hasFocusListener, boolean isFocusPolicy) {
|
||||
AudioSystem.setDynamicPolicyCallback(mDynPolicyCallback);
|
||||
|
||||
if (DEBUG_AP) Log.d(TAG, "registerAudioPolicy for " + pcb.asBinder()
|
||||
@ -6451,7 +6454,8 @@ public class AudioService extends IAudioService.Stub
|
||||
Slog.e(TAG, "Cannot re-register policy");
|
||||
return null;
|
||||
}
|
||||
AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener);
|
||||
AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener,
|
||||
isFocusPolicy);
|
||||
pcb.asBinder().linkToDeath(app, 0/*flags*/);
|
||||
regId = app.getRegistrationId();
|
||||
mAudioPolicies.put(pcb.asBinder(), app);
|
||||
@ -6649,15 +6653,21 @@ public class AudioService extends IAudioService.Stub
|
||||
* is handling ducking for audio focus.
|
||||
*/
|
||||
int mFocusDuckBehavior = AudioPolicy.FOCUS_POLICY_DUCKING_DEFAULT;
|
||||
boolean mIsFocusPolicy = false;
|
||||
|
||||
AudioPolicyProxy(AudioPolicyConfig config, IAudioPolicyCallback token,
|
||||
boolean hasFocusListener) {
|
||||
boolean hasFocusListener, boolean isFocusPolicy) {
|
||||
super(config);
|
||||
setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++));
|
||||
mPolicyCallback = token;
|
||||
mHasFocusListener = hasFocusListener;
|
||||
if (mHasFocusListener) {
|
||||
mMediaFocusControl.addFocusFollower(mPolicyCallback);
|
||||
// can only ever be true if there is a focus listener
|
||||
if (isFocusPolicy) {
|
||||
mIsFocusPolicy = true;
|
||||
mMediaFocusControl.setFocusPolicy(mPolicyCallback);
|
||||
}
|
||||
}
|
||||
connectMixes();
|
||||
}
|
||||
@ -6675,6 +6685,9 @@ public class AudioService extends IAudioService.Stub
|
||||
}
|
||||
|
||||
void release() {
|
||||
if (mIsFocusPolicy) {
|
||||
mMediaFocusControl.unsetFocusPolicy(mPolicyCallback);
|
||||
}
|
||||
if (mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) {
|
||||
mMediaFocusControl.setDuckingInExtPolicyAvailable(false);
|
||||
}
|
||||
@ -6689,6 +6702,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 =
|
||||
new HashMap<IBinder, AudioPolicyProxy>();
|
||||
private int mAudioPolicyCounter = 0; // always accessed synchronized on mAudioPolicies
|
||||
|
@ -33,7 +33,7 @@ import java.io.PrintWriter;
|
||||
* @hide
|
||||
* 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
|
||||
* stack to its release.
|
||||
* stack, or the map of focus owners for an external focus policy, to its release.
|
||||
*/
|
||||
public class FocusRequester {
|
||||
|
||||
@ -101,6 +101,21 @@ public class FocusRequester {
|
||||
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) {
|
||||
try {
|
||||
@ -118,6 +133,10 @@ public class FocusRequester {
|
||||
return (mSourceRef != null) && mSourceRef.equals(ib);
|
||||
}
|
||||
|
||||
boolean hasSameDispatcher(IAudioFocusDispatcher fd) {
|
||||
return (mFocusDispatcher != null) && mFocusDispatcher.equals(fd);
|
||||
}
|
||||
|
||||
boolean hasSamePackage(String pack) {
|
||||
try {
|
||||
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() {
|
||||
return new AudioFocusInfo(mAttributes, mCallingUid, mClientId, mPackageName,
|
||||
mFocusGainRequest, mFocusLossReceived, mGrantFlags);
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.server.audio;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.media.AudioAttributes;
|
||||
@ -23,6 +24,7 @@ import android.media.AudioFocusInfo;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioSystem;
|
||||
import android.media.IAudioFocusDispatcher;
|
||||
import android.media.audiopolicy.AudioPolicy;
|
||||
import android.media.audiopolicy.IAudioPolicyCallback;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
@ -32,7 +34,10 @@ import android.util.Log;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.text.DateFormat;
|
||||
|
||||
@ -43,6 +48,7 @@ import java.text.DateFormat;
|
||||
public class MediaFocusControl implements PlayerFocusEnforcer {
|
||||
|
||||
private static final String TAG = "MediaFocusControl";
|
||||
static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* 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()) {
|
||||
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(" 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:
|
||||
* 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() {
|
||||
synchronized(mAudioFocusLock) {
|
||||
if (mFocusPolicy != null) {
|
||||
removeFocusEntryForExtPolicy(mCb);
|
||||
} else {
|
||||
removeFocusStackEntryOnDeath(mCb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@ -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() {
|
||||
synchronized(mAudioFocusLock) {
|
||||
if (mFocusStack.empty()) {
|
||||
@ -487,10 +651,23 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
|
||||
& (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
|
||||
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;
|
||||
if (!canReassignAudioFocus()) {
|
||||
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 {
|
||||
// request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
|
||||
// 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
|
||||
// (premature death == death before abandoning focus)
|
||||
// Register for client death notification
|
||||
@ -569,7 +754,8 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
|
||||
/**
|
||||
* @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
|
||||
Log.i(TAG, " AudioFocus abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
|
||||
+ "/" + Binder.getCallingPid()
|
||||
@ -577,6 +763,16 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
|
||||
try {
|
||||
// this will take care of notifying the new focus owner if needed
|
||||
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
|
||||
& (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
|
||||
if (exitingRingOrCall) { mRingOrCallActive = false; }
|
||||
|
Reference in New Issue
Block a user