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:
@ -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);
|
||||
|
Reference in New Issue
Block a user