summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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; }