From bec223b3375cff5a89929533105e91bb31e610e7 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Wed, 8 Dec 2021 18:40:08 -0800 Subject: [PATCH] Audio focus: fix delayed focus request during locked focus behavior Fix issue where if audio focus was requested with GAIN, and accepted delayed focus, while focus was locked, then previous focus owners didn't receive LOSS, and thus would be removed from the stack. The fix consists in propagating the focus loss through the stack under the focus locker, and remove any focus loser if the loss was definitive. In order to simulate the bug conditions, the test API for simulating focus requests and abandons, was modified to support locking the focus like in a phone call. Bug: 206856107 Test: atest AudioFocusTest#testAudioFocusDelayedByCall Test: atest AudioFocusTest#testAudioFocusTransientDelayedByCall Change-Id: I211cd46b66c882499bfa43718bc0cb37b2f79817 --- media/java/android/media/AudioManager.java | 7 +-- media/java/android/media/IAudioService.aidl | 2 +- .../android/server/audio/AudioService.java | 7 ++- .../server/audio/MediaFocusControl.java | 43 ++++++++++++++++--- 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index d721291ad78f4..0722417c0d18c 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4003,8 +4003,7 @@ public class AudioManager { * @hide * flag set on test API calls, * see {@link #requestAudioFocusForTest(AudioFocusRequest, String, int, int)}, - * note that it isn't used in conjunction with other flags, it is passed as the single - * value for flags */ + */ public static final int AUDIOFOCUS_FLAG_TEST = 0x1 << 3; /** @hide */ public static final int AUDIOFOCUS_FLAGS_APPS = AUDIOFOCUS_FLAG_DELAY_OK @@ -4187,7 +4186,9 @@ public class AudioManager { afr.getFocusGain(), mICallBack, mAudioFocusDispatcher, - clientFakeId, "com.android.test.fakeclient", clientFakeUid, clientTargetSdk); + clientFakeId, "com.android.test.fakeclient", + afr.getFlags() | AudioManager.AUDIOFOCUS_FLAG_TEST, + clientFakeUid, clientTargetSdk); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index afcbc5769cf0c..147e73599cadc 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -384,7 +384,7 @@ interface IAudioService { int requestAudioFocusForTest(in AudioAttributes aa, int durationHint, IBinder cb, in IAudioFocusDispatcher fd, in String clientId, in String callingPackageName, - int uid, int sdk); + int flags, int uid, int sdk); int abandonAudioFocusForTest(in IAudioFocusDispatcher fd, in String clientId, in AudioAttributes aa, in String callingPackageName); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index aa33644357be3..6238198e94889 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -8252,6 +8252,9 @@ public class AudioService extends IAudioService.Stub public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb, IAudioFocusDispatcher fd, String clientId, String callingPackageName, String attributionTag, int flags, IAudioPolicyCallback pcb, int sdk) { + if ((flags & AudioManager.AUDIOFOCUS_FLAG_TEST) != 0) { + throw new IllegalArgumentException("Invalid test flag"); + } final int uid = Binder.getCallingUid(); MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "focus") .setUid(uid) @@ -8310,7 +8313,7 @@ public class AudioService extends IAudioService.Stub /** see {@link AudioManager#requestAudioFocusForTest(AudioFocusRequest, String, int, int)} */ public int requestAudioFocusForTest(AudioAttributes aa, int durationHint, IBinder cb, IAudioFocusDispatcher fd, String clientId, String callingPackageName, - int fakeUid, int sdk) { + int flags, int fakeUid, int sdk) { if (!enforceQueryAudioStateForTest("focus request")) { return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } @@ -8320,7 +8323,7 @@ public class AudioService extends IAudioService.Stub return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd, - clientId, callingPackageName, null, AudioManager.AUDIOFOCUS_FLAG_TEST, + clientId, callingPackageName, null, flags, sdk, false /*forceDuck*/, fakeUid); } diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index bf49ac85d1dcb..66f62355c7f06 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -461,13 +461,19 @@ public class MediaFocusControl implements PlayerFocusEnforcer { * at the top of the focus stack * Push the focus requester onto the audio focus stack at the first position immediately * following the locked focus owners. + * Propagate through the stack the changes that the new (future) focus owner causes. + * @param nfr the future focus owner that will gain focus when the locked focus owners are + * removed from the stack * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or * {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED} */ @GuardedBy("mAudioFocusLock") - private int pushBelowLockedFocusOwners(FocusRequester nfr) { + private int pushBelowLockedFocusOwnersAndPropagate(FocusRequester nfr) { + if (DEBUG) { + Log.v(TAG, "pushBelowLockedFocusOwnersAndPropagate client=" + nfr.getClientId()); + } int lastLockedFocusOwnerIndex = mFocusStack.size(); - for (int index = mFocusStack.size()-1; index >= 0; index--) { + for (int index = mFocusStack.size() - 1; index >= 0; index--) { if (isLockedFocusOwner(mFocusStack.elementAt(index))) { lastLockedFocusOwnerIndex = index; } @@ -480,10 +486,33 @@ public class MediaFocusControl implements PlayerFocusEnforcer { propagateFocusLossFromGain_syncAf(nfr.getGainRequest(), nfr, false /*forceDuck*/); mFocusStack.push(nfr); return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; - } else { - mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex); - return AudioManager.AUDIOFOCUS_REQUEST_DELAYED; } + + if (DEBUG) { + Log.v(TAG, "> lastLockedFocusOwnerIndex=" + lastLockedFocusOwnerIndex); + } + mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex); + + // propagate potential focus loss (and removal from stack) after the newly + // inserted FocusRequester (at index lastLockedFocusOwnerIndex-1) + final List clientsToRemove = new LinkedList(); + for (int index = lastLockedFocusOwnerIndex - 1; index >= 0; index--) { + final boolean isDefinitiveLoss = + mFocusStack.elementAt(index).handleFocusLossFromGain( + nfr.getGainRequest(), nfr, false /*forceDuck*/); + if (isDefinitiveLoss) { + clientsToRemove.add(mFocusStack.elementAt(index).getClientId()); + } + } + for (String clientToRemove : clientsToRemove) { + if (DEBUG) { + Log.v(TAG, "> removing focus client " + clientToRemove); + } + removeFocusStackEntry(clientToRemove, false /*signal*/, + true /*notifyFocusFollowers*/); + } + + return AudioManager.AUDIOFOCUS_REQUEST_DELAYED; } /** @@ -868,7 +897,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { * @param forceDuck only true if * {@link android.media.AudioFocusRequest.Builder#setFocusGain(int)} was set to true for * accessibility. - * @param testUid ignored if flags is not AudioManager.AUDIOFOCUS_FLAG_TEST (strictly equals to) + * @param testUid ignored if flags doesn't contain AudioManager.AUDIOFOCUS_FLAG_TEST * otherwise the UID being injected for testing * @return */ @@ -1029,7 +1058,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { if (focusGrantDelayed) { // focusGrantDelayed being true implies we can't reassign focus right now // which implies the focus stack is not empty. - final int requestResult = pushBelowLockedFocusOwners(nfr); + final int requestResult = pushBelowLockedFocusOwnersAndPropagate(nfr); if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) { notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult); }