Merge "Audio focus: API for external audio focus policy" into oc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
4d52ae9707
@ -22842,6 +22842,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();
|
||||
@ -25844,8 +25845,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 {
|
||||
@ -25860,6 +25863,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;
|
||||
@ -5634,8 +5635,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) {
|
||||
@ -5650,6 +5652,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(
|
||||
@ -6430,7 +6433,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()
|
||||
@ -6452,7 +6455,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);
|
||||
@ -6650,15 +6654,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();
|
||||
}
|
||||
@ -6676,6 +6686,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);
|
||||
}
|
||||
@ -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 =
|
||||
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