Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 52441f82 authored by Jean-Michel Trivi's avatar Jean-Michel Trivi
Browse files

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
parent 609258f8
Loading
Loading
Loading
Loading
+14 −6
Original line number Diff line number Diff line
@@ -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) {
+56 −1
Original line number Diff line number Diff line
@@ -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;
        }
    }
}
+5 −1
Original line number Diff line number Diff line
@@ -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);
    }

    //=================================================================