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