From 958876fe55ea0fdeb73c72240a2f2bab32833443 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Sun, 16 Nov 2014 15:40:22 -0800 Subject: [PATCH] Add support for audio focus locking New API for a registered AudioPolicy to lock/unlock the audio focus stack and prevent any new grant of focus, similar to the way phone calls behave. Bug 16010554 Change-Id: If34a58ca9bd43d5479e94a2a7b540750b4c6efe9 --- media/java/android/media/AudioManager.java | 119 ++++++++++++++---- media/java/android/media/AudioService.java | 33 ++++- media/java/android/media/FocusRequester.java | 17 ++- media/java/android/media/IAudioService.aidl | 5 +- .../java/android/media/MediaFocusControl.java | 36 +++--- 5 files changed, 166 insertions(+), 44 deletions(-) diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 645681a38de4b..3f083057e9960 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -17,6 +17,7 @@ package android.media; import android.Manifest; +import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; @@ -2318,14 +2319,25 @@ public class AudioManager { return status; } - // when adding new flags, add them to AUDIOFOCUS_FLAGS_ALL + // when adding new flags, add them to the relevant AUDIOFOCUS_FLAGS_APPS or SYSTEM masks /** @hide */ + @SystemApi public static final int AUDIOFOCUS_FLAG_DELAY_OK = 0x1 << 0; /** @hide */ - public static final int AUDIOFOCUS_FLAGS_ALL = AUDIOFOCUS_FLAG_DELAY_OK; + @SystemApi + public static final int AUDIOFOCUS_FLAG_LOCK = 0x1 << 1; + /** @hide */ + public static final int AUDIOFOCUS_FLAGS_APPS = AUDIOFOCUS_FLAG_DELAY_OK; + /** @hide */ + public static final int AUDIOFOCUS_FLAGS_SYSTEM = AUDIOFOCUS_FLAG_DELAY_OK + | AUDIOFOCUS_FLAG_LOCK; /** * @hide + * Request audio focus. + * Send a request to obtain the audio focus. This method differs from + * {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)} in that it can express + * that the requester accepts delayed grants of audio focus. * @param l the listener to be notified of audio focus changes. It is not allowed to be null * when the request is flagged with {@link #AUDIOFOCUS_FLAG_DELAY_OK}. * @param requestAttributes non null {@link AudioAttributes} describing the main reason for @@ -2340,24 +2352,70 @@ public class AudioManager { * usecases such as voice memo recording, or speech recognition. * Use {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such * as the playback of a song or a video. - * @param flags use 0 when not using any flags for the request, which behaves like - * {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio - * focus is granted immediately, or the grant request fails because the system is in a - * state where focus cannot change (e.g. a phone call). - * Use {link #AUDIOFOCUS_FLAG_DELAY_OK} if it is ok for the requester to not be granted - * audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when - * the system is in a state where focus cannot change, but be granted focus later when - * this condition ends. + * @param flags 0 or {link #AUDIOFOCUS_FLAG_DELAY_OK}. + *
Use 0 when not using any flags for the request, which behaves like + * {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio + * focus is granted immediately, or the grant request fails because the system is in a + * state where focus cannot change (e.g. a phone call). + *
Use {link #AUDIOFOCUS_FLAG_DELAY_OK} if it is ok for the requester to not be granted + * audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when + * the system is in a state where focus cannot change, but be granted focus later when + * this condition ends. * @return {@link #AUDIOFOCUS_REQUEST_FAILED}, {@link #AUDIOFOCUS_REQUEST_GRANTED} * or {@link #AUDIOFOCUS_REQUEST_DELAYED}. * The return value is never {@link #AUDIOFOCUS_REQUEST_DELAYED} when focus is requested * without the {@link #AUDIOFOCUS_FLAG_DELAY_OK} flag. * @throws IllegalArgumentException */ + @SystemApi public int requestAudioFocus(OnAudioFocusChangeListener l, - AudioAttributes requestAttributes, + @NonNull AudioAttributes requestAttributes, int durationHint, int flags) throws IllegalArgumentException { + if (flags != (flags & AUDIOFOCUS_FLAGS_APPS)) { + throw new IllegalArgumentException("Invalid flags 0x" + + Integer.toHexString(flags).toUpperCase()); + } + return requestAudioFocus(l, requestAttributes, durationHint, + flags & AUDIOFOCUS_FLAGS_APPS, + null /* no AudioPolicy*/); + } + + /** + * @hide + * Request or lock audio focus. + * This method is to be used by system components that have registered an + * {@link android.media.audiopolicy.AudioPolicy} to request audio focus, but also to "lock" it + * so focus granting is temporarily disabled. + * @param l see the description of the same parameter in + * {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)} + * @param requestAttributes non null {@link AudioAttributes} describing the main reason for + * requesting audio focus. + * @param durationHint see the description of the same parameter in + * {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)} + * @param flags 0 or a combination of {link #AUDIOFOCUS_FLAG_DELAY_OK}, + * {@link #AUDIOFOCUS_FLAG_LOCK} + *
Use 0 when not using any flags for the request, which behaves like + * {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio + * focus is granted immediately, or the grant request fails because the system is in a + * state where focus cannot change (e.g. a phone call). + *
Use {link #AUDIOFOCUS_FLAG_DELAY_OK} if it is ok for the requester to not be granted + * audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when + * the system is in a state where focus cannot change, but be granted focus later when + * this condition ends. + *
Use {@link #AUDIOFOCUS_FLAG_LOCK} when locking audio focus so granting is + * temporarily disabled. + * @param ap a registered {@link android.media.audiopolicy.AudioPolicy} instance when locking + * focus, or null. + * @return see the description of the same return value in + * {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)} + * @throws IllegalArgumentException + */ + public int requestAudioFocus(OnAudioFocusChangeListener l, + @NonNull AudioAttributes requestAttributes, + int durationHint, + int flags, + AudioPolicy ap) throws IllegalArgumentException { // parameter checking if (requestAttributes == null) { throw new IllegalArgumentException("Illegal null AudioAttributes argument"); @@ -2366,7 +2424,7 @@ public class AudioManager { (durationHint > AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) { throw new IllegalArgumentException("Invalid duration hint"); } - if (flags != (flags & AUDIOFOCUS_FLAGS_ALL)) { + if (flags != (flags & AUDIOFOCUS_FLAGS_SYSTEM)) { throw new IllegalArgumentException("Illegal flags 0x" + Integer.toHexString(flags).toUpperCase()); } @@ -2374,6 +2432,10 @@ public class AudioManager { throw new IllegalArgumentException( "Illegal null focus listener when flagged as accepting delayed focus grant"); } + if (((flags & AUDIOFOCUS_FLAG_LOCK) == AUDIOFOCUS_FLAG_LOCK) && (ap == null)) { + throw new IllegalArgumentException( + "Illegal null audio policy when locking audio focus"); + } int status = AUDIOFOCUS_REQUEST_FAILED; registerAudioFocusListener(l); @@ -2381,9 +2443,10 @@ public class AudioManager { try { status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack, mAudioFocusDispatcher, getIdForAudioFocusListener(l), - mContext.getOpPackageName() /* package name */, flags); + mContext.getOpPackageName() /* package name */, flags, + ap != null ? ap.token() : null); } catch (RemoteException e) { - Log.e(TAG, "Can't call requestAudioFocus() on AudioService due to "+e); + Log.e(TAG, "Can't call requestAudioFocus() on AudioService:", e); } return status; } @@ -2405,9 +2468,11 @@ public class AudioManager { .setInternalLegacyStreamType(streamType).build(), durationHint, mICallBack, null, MediaFocusControl.IN_VOICE_COMM_FOCUS_ID, - mContext.getOpPackageName(), 0 /* flags, legacy behavior*/ ); + mContext.getOpPackageName(), + AUDIOFOCUS_FLAG_LOCK, + null /* policy token */); } catch (RemoteException e) { - Log.e(TAG, "Can't call requestAudioFocusForCall() on AudioService due to "+e); + Log.e(TAG, "Can't call requestAudioFocusForCall() on AudioService:", e); } } @@ -2420,9 +2485,10 @@ public class AudioManager { public void abandonAudioFocusForCall() { IAudioService service = getService(); try { - service.abandonAudioFocus(null, MediaFocusControl.IN_VOICE_COMM_FOCUS_ID); + service.abandonAudioFocus(null, MediaFocusControl.IN_VOICE_COMM_FOCUS_ID, + null /*AudioAttributes, legacy behavior*/); } catch (RemoteException e) { - Log.e(TAG, "Can't call abandonAudioFocusForCall() on AudioService due to "+e); + Log.e(TAG, "Can't call abandonAudioFocusForCall() on AudioService:", e); } } @@ -2432,19 +2498,30 @@ public class AudioManager { * @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED} */ public int abandonAudioFocus(OnAudioFocusChangeListener l) { + return abandonAudioFocus(l, null /*AudioAttributes, legacy behavior*/); + } + + /** + * @hide + * Abandon audio focus. Causes the previous focus owner, if any, to receive focus. + * @param l the listener with which focus was requested. + * @param aa the {@link AudioAttributes} with which audio focus was requested + * @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED} + */ + @SystemApi + public int abandonAudioFocus(OnAudioFocusChangeListener l, AudioAttributes aa) { int status = AUDIOFOCUS_REQUEST_FAILED; unregisterAudioFocusListener(l); IAudioService service = getService(); try { status = service.abandonAudioFocus(mAudioFocusDispatcher, - getIdForAudioFocusListener(l)); + getIdForAudioFocusListener(l), aa); } catch (RemoteException e) { - Log.e(TAG, "Can't call abandonAudioFocus() on AudioService due to "+e); + Log.e(TAG, "Can't call abandonAudioFocus() on AudioService:", e); } return status; } - //==================================================================== // Remote Control /** diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index c70ac55942700..b6b24a4c76815 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -49,6 +49,7 @@ import android.hardware.usb.UsbManager; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.audiopolicy.AudioMix; +import android.media.audiopolicy.AudioPolicy; import android.media.audiopolicy.AudioPolicyConfig; import android.os.Binder; import android.os.Build; @@ -5018,13 +5019,34 @@ public class AudioService extends IAudioService.Stub { // Audio Focus //========================================================================================== public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb, - IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) { + IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags, + IBinder policyToken) { + // permission checks + if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) { + if (mMediaFocusControl.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) { + if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_PHONE_STATE)) { + Log.e(TAG, "Invalid permission to (un)lock audio focus", new Exception()); + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + } else { + // only a registered audio policy can be used to lock focus + synchronized (mAudioPolicies) { + if (!mAudioPolicies.containsKey(policyToken)) { + Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus", + new Exception()); + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + } + } + } + return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd, clientId, callingPackageName, flags); } - public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId) { - return mMediaFocusControl.abandonAudioFocus(fd, clientId); + public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa) { + return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa); } public void unregisterAudioFocusClient(String clientId) { @@ -5725,6 +5747,9 @@ public class AudioService extends IAudioService.Stub { // TODO implement clearing mix attribute matching info in native audio policy } + //====================== + // Audio policy proxy + //====================== /** * This internal class inherits from AudioPolicyConfig which contains all the mixes and * their configurations. @@ -5742,8 +5767,8 @@ public class AudioService extends IAudioService.Stub { public void binderDied() { synchronized (mAudioPolicies) { Log.i(TAG, "audio policy " + mToken + " died"); - mAudioPolicies.remove(mToken); disconnectMixes(); + mAudioPolicies.remove(mToken); } } diff --git a/media/java/android/media/FocusRequester.java b/media/java/android/media/FocusRequester.java index 682d54c82deb6..0d675fc835b44 100644 --- a/media/java/android/media/FocusRequester.java +++ b/media/java/android/media/FocusRequester.java @@ -83,6 +83,10 @@ class FocusRequester { } } + boolean isLockedFocusOwner() { + return ((mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0); + } + boolean hasSameBinder(IBinder ib) { return (mSourceRef != null) && mSourceRef.equals(ib); } @@ -99,6 +103,9 @@ class FocusRequester { return mCallingUid == uid; } + String getClientId() { + return mClientId; + } int getGainRequest() { return mFocusGainRequest; @@ -144,12 +151,20 @@ class FocusRequester { return focusChangeToString(mFocusLossReceived); } + private static String flagsToString(int flags) { + String msg = new String(); + if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) != 0) { msg += "DELAY_OK"; } + if (!msg.isEmpty()) { msg += "|"; } + if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0) { msg += "LOCK"; } + return msg; + } + void dump(PrintWriter pw) { pw.println(" source:" + mSourceRef + " -- pack: " + mPackageName + " -- client: " + mClientId + " -- gain: " + focusGainToString() - + " -- grant: " + mGrantFlags + + " -- flags: " + flagsToString(mGrantFlags) + " -- loss: " + focusLossToString() + " -- uid: " + mCallingUid + " -- attr: " + mAttributes); diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 47a52919514bb..b6914471e68a9 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -118,9 +118,10 @@ interface IAudioService { boolean isBluetoothA2dpOn(); int requestAudioFocus(in AudioAttributes aa, int durationHint, IBinder cb, - IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags); + IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags, + IBinder policyToken); - int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId); + int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, in AudioAttributes aa); void unregisterAudioFocusClient(String clientId); diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java index c495106ddd1ba..8d96970e3bf2a 100644 --- a/media/java/android/media/MediaFocusControl.java +++ b/media/java/android/media/MediaFocusControl.java @@ -390,7 +390,8 @@ public class MediaFocusControl implements OnFinished { // AudioFocus //========================================================================================== - /* constant to identify focus stack entry that is used to hold the focus while the phone + /** + * Constant to identify a focus stack entry that is used to hold the focus while the phone * is ringing or during a call. Used by com.android.internal.telephony.CallManager when * entering and exiting calls. */ @@ -539,40 +540,40 @@ public class MediaFocusControl implements OnFinished { * 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 - * implies that an "exclusive" focus owner is at the top of the focus stack. + * implies that an "locked" focus owner is at the top of the focus stack. * Modifications to the implementation that break this assumption will cause focus requests to * misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag. */ private boolean canReassignAudioFocus() { // focus requests are rejected during a phone call or when the phone is ringing // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus - if (!mFocusStack.isEmpty() && isExclusiveFocusOwner(mFocusStack.peek())) { + if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) { return false; } return true; } - private boolean isExclusiveFocusOwner(FocusRequester fr) { - return fr.hasSameClient(IN_VOICE_COMM_FOCUS_ID); + private boolean isLockedFocusOwner(FocusRequester fr) { + return (fr.hasSameClient(IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner()); } /** * Helper function - * Pre-conditions: focus stack is not empty, there is one or more exclusive focus owner + * Pre-conditions: focus stack is not empty, there is one or more locked focus owner * at the top of the focus stack * Push the focus requester onto the audio focus stack at the first position immediately - * following the exclusive focus owners. + * following the locked focus owners. * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or * {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED} */ - private int pushBelowExclusiveFocusOwners(FocusRequester nfr) { - int lastExclusiveFocusOwnerIndex = mFocusStack.size(); + private int pushBelowLockedFocusOwners(FocusRequester nfr) { + int lastLockedFocusOwnerIndex = mFocusStack.size(); for (int index = mFocusStack.size()-1; index >= 0; index--) { - if (isExclusiveFocusOwner(mFocusStack.elementAt(index))) { - lastExclusiveFocusOwnerIndex = index; + if (isLockedFocusOwner(mFocusStack.elementAt(index))) { + lastLockedFocusOwnerIndex = index; } } - if (lastExclusiveFocusOwnerIndex == mFocusStack.size()) { + if (lastLockedFocusOwnerIndex == mFocusStack.size()) { // this should not happen, but handle it and log an error Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()", new Exception()); @@ -581,7 +582,7 @@ public class MediaFocusControl implements OnFinished { mFocusStack.push(nfr); return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } else { - mFocusStack.insertElementAt(nfr, lastExclusiveFocusOwnerIndex); + mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex); return AudioManager.AUDIOFOCUS_REQUEST_DELAYED; } } @@ -687,7 +688,7 @@ public class MediaFocusControl implements OnFinished { if (focusGrantDelayed) { // focusGrantDelayed being true implies we can't reassign focus right now // which implies the focus stack is not empty. - return pushBelowExclusiveFocusOwners(nfr); + return pushBelowLockedFocusOwners(nfr); } else { // propagate the focus change through the stack if (!mFocusStack.empty()) { @@ -703,8 +704,11 @@ public class MediaFocusControl implements OnFinished { return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; } - /** @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener) */ - protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId) { + /** + * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes) + * */ + protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) { + // AudioAttributes are currently ignored, to be used for zones Log.i(TAG, " AudioFocus abandonAudioFocus() from " + clientId); try { // this will take care of notifying the new focus owner if needed