diff options
| author | 2021-09-26 18:52:44 -0700 | |
|---|---|---|
| committer | 2021-09-27 15:20:29 +0000 | |
| commit | 52441f8285b129c2b7546958b5bca6d4ecece704 (patch) | |
| tree | 67d4f1d0eb852e8eedbf5cc32f37b09433ae3e5f | |
| parent | 609258f83de63b9a4ba4ff2d64b8ba7fd445c8ac (diff) | |
Audio focus: make media fade out temporary
This change only affect media/game apps that request and lose focus
with GAIN and LOSS (i.e. no temporary gain/loss).
The fade out mechanism for media apps losing audio focus intentionally
left players in a muted state at the end of the fade out ramp. If
they were starting again after a focus request, they were unmuted.
But some applications don't respect audio focus, e.g. they request focus
with GAIN when they start playing, but keep playing even after they lose
focus with LOSS. Such "offending" apps were left muted as they were not
expected to keep playing.
This change is a mitigation of the behavior of offending apps: 2s
after an app has been notified it lost focus, if it still had
players, they will be unmuted.
- for apps that followed the audio focus guidelines, their player
was paused by then, so no change expected
- for offending apps, their audio will be heard again, shortly
after the new app (the new focus owner) is likely to have started
playing.
Bug: 196186950
Test: atest AudioFocusTest
Change-Id: I873fd1371ae499e50f5e6a60456ce4b0139f2d34
3 files changed, 75 insertions, 8 deletions
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java index bb627e5a21fb..00cb280236d7 100644 --- a/services/core/java/com/android/server/audio/FadeOutManager.java +++ b/services/core/java/com/android/server/audio/FadeOutManager.java @@ -36,7 +36,16 @@ public final class FadeOutManager { public static final String TAG = "AudioService.FadeOutManager"; + /** duration of the fade out curve */ /*package*/ static final long FADE_OUT_DURATION_MS = 2000; + /** + * delay after which a faded out player will be faded back in. This will be heard by the user + * only in the case of unmuting players that didn't respect audio focus and didn't stop/pause + * when their app lost focus. + * This is the amount of time between the app being notified of + * the focus loss (when its muted by the fade out), and the time fade in (to unmute) starts + */ + /*package*/ static final long DELAY_FADE_IN_OFFENDERS_MS = 2000; private static final boolean DEBUG = PlaybackActivityMonitor.DEBUG; @@ -148,6 +157,11 @@ public final class FadeOutManager { } } + /** + * Remove the app for the given UID from the list of faded out apps, unfade out its players + * @param uid the uid for the app to unfade out + * @param players map of current available players (so we can get an APC from piid) + */ synchronized void unfadeOutUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) { Log.i(TAG, "unfadeOutUid() uid:" + uid); final FadedOutApp fa = mFadedApps.remove(uid); @@ -157,12 +171,6 @@ public final class FadeOutManager { fa.removeUnfadeAll(players); } - synchronized void forgetUid(int uid) { - //Log.v(TAG, "forget() uid:" + uid); - //mFadedApps.remove(uid); - // TODO unfade all players later in case they are reused or the app continued to play - } - // pre-condition: apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED // see {@link PlaybackActivityMonitor#playerEvent} synchronized void checkFade(@NonNull AudioPlaybackConfiguration apc) { diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index e6c4abfa2086..9548ada14b8e 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -131,6 +131,11 @@ public class MediaFocusControl implements PlayerFocusEnforcer { @Override public void restoreVShapedPlayers(@NonNull FocusRequester winner) { mFocusEnforcer.restoreVShapedPlayers(winner); + // remove scheduled events to unfade out offending players (if any) corresponding to + // this uid, as we're removing any effects of muting/ducking/fade out now + mFocusHandler.removeEqualMessages(MSL_L_FORGET_UID, + new ForgetFadeUidInfo(winner.getClientUid())); + } @Override @@ -1182,6 +1187,13 @@ public class MediaFocusControl implements PlayerFocusEnforcer { mFocusHandler.obtainMessage(MSG_L_FOCUS_LOSS_AFTER_FADE, focusLoser), FadeOutManager.FADE_OUT_DURATION_MS); } + + private void postForgetUidLater(int uid) { + mFocusHandler.sendMessageDelayed( + mFocusHandler.obtainMessage(MSL_L_FORGET_UID, new ForgetFadeUidInfo(uid)), + FadeOutManager.DELAY_FADE_IN_OFFENDERS_MS); + } + //================================================================= // Message handling private Handler mFocusHandler; @@ -1196,6 +1208,8 @@ public class MediaFocusControl implements PlayerFocusEnforcer { */ private static final int MSG_L_FOCUS_LOSS_AFTER_FADE = 1; + private static final int MSL_L_FORGET_UID = 2; + private void initFocusThreading() { mFocusThread = new HandlerThread(TAG); mFocusThread.start(); @@ -1213,15 +1227,56 @@ public class MediaFocusControl implements PlayerFocusEnforcer { if (loser.isInFocusLossLimbo()) { loser.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS); loser.release(); - mFocusEnforcer.forgetUid(loser.getClientUid()); + postForgetUidLater(loser.getClientUid()); } } break; + + case MSL_L_FORGET_UID: + final int uid = ((ForgetFadeUidInfo) msg.obj).mUid; + if (DEBUG) { + Log.d(TAG, "MSL_L_FORGET_UID uid=" + uid); + } + mFocusEnforcer.forgetUid(uid); + break; default: break; } } }; + } + /** + * Class to associate a UID with a scheduled event to "forget" a UID for the fade out behavior. + * Having a class with an equals() override allows using Handler.removeEqualsMessage() to + * unschedule events when needed. Here we need to unschedule the "unfading out" == "forget uid" + * whenever a new, more recent, focus related event happens before this one is handled. + */ + private static final class ForgetFadeUidInfo { + private final int mUid; + + ForgetFadeUidInfo(int uid) { + mUid = uid; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ForgetFadeUidInfo f = (ForgetFadeUidInfo) o; + if (f.mUid != mUid) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return mUid; + } } } diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index a13b2eb344d9..b94cea4d5d40 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -747,7 +747,11 @@ public final class PlaybackActivityMonitor @Override public void forgetUid(int uid) { - mFadingManager.forgetUid(uid); + final HashMap<Integer, AudioPlaybackConfiguration> players; + synchronized (mPlayerLock) { + players = (HashMap<Integer, AudioPlaybackConfiguration>) mPlayers.clone(); + } + mFadingManager.unfadeOutUid(uid, players); } //================================================================= |