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 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;
}

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
* 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 {

View File

@ -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
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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,7 +335,11 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
public void binderDied() {
synchronized(mAudioFocusLock) {
removeFocusStackEntryOnDeath(mCb);
if (mFocusPolicy != null) {
removeFocusEntryForExtPolicy(mCb);
} else {
removeFocusStackEntryOnDeath(mCb);
}
}
}
}
@ -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; }