summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jean-Michel Trivi <jmtrivi@google.com> 2017-04-02 23:19:02 -0700
committer Jean-Michel Trivi <jmtrivi@google.com> 2017-04-03 14:44:47 -0700
commit126cf03f7c8e825a23e47dd133e77eb1b3b7633d (patch)
treec3f979ffd4bfed9644bcc3aaa79f9ff2aa5e3727
parent8371f96e9967c93e482dfb995c6092b2e7b78cec (diff)
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
-rw-r--r--api/system-current.txt4
-rw-r--r--media/java/android/media/AudioManager.java44
-rw-r--r--media/java/android/media/IAudioService.aidl9
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicy.java89
-rw-r--r--media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl5
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java39
-rw-r--r--services/core/java/com/android/server/audio/FocusRequester.java50
-rw-r--r--services/core/java/com/android/server/audio/MediaFocusControl.java202
8 files changed, 424 insertions, 18 deletions
diff --git a/api/system-current.txt b/api/system-current.txt
index 3e739d9f13a2..887a0253fd2d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -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;
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index c7796cdd4cc4..4adbf79d562c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2540,6 +2540,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
* when ringing ends and the call is rejected or not answered.
* Should match one or more calls to {@link #requestAudioFocusForCall(int, int)}.
@@ -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 {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 58559843f1f9..884d41e59ada 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -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
}
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 423b4678441c..61d642f50179 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -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.
@@ -179,6 +185,21 @@ public class AudioPolicy {
}
/**
+ * 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);
}
diff --git a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
index ad8af15b15f2..86abbb4dc8d9 100644
--- a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
+++ b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
@@ -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);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 49d1521e378f..9a38f4d230fc 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -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
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index 5275c0524d54..bcaa29593304 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -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);
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index b3f1548d4d92..821e78a27e33 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -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");
@@ -234,6 +247,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.
* The implementation guarantees that a state where focus cannot be immediately reassigned
@@ -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; }