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

Commit d58d7132 authored by Jean-Michel Trivi's avatar Jean-Michel Trivi Committed by android-build-merger
Browse files

Merge "Audio service: only duck started players + refactor" into oc-dev am: 67b62f2d

am: 7e07c5b0

Change-Id: I132d7b599b9dd0fe674c029c06eaa3753d6e1b6c
parents f96e2a19 7e07c5b0
Loading
Loading
Loading
Loading
+144 −88
Original line number Diff line number Diff line
@@ -18,12 +18,10 @@ package com.android.server.audio;

import android.annotation.NonNull;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioSystem;
import android.media.IPlaybackConfigDispatcher;
import android.media.MediaRecorder;
import android.media.PlayerBase;
import android.media.VolumeShaper;
import android.os.Binder;
@@ -99,13 +97,6 @@ public final class PlaybackActivityMonitor
        apc.init();
        synchronized(mPlayerLock) {
            mPlayers.put(newPiid, apc);
            if (mDuckedUids.contains(new Integer(apc.getClientUid()))) {
                if (DEBUG) { Log.v(TAG, "  > trackPlayer() piid=" + newPiid + " must be ducked"); }
                mDuckedPlayers.add(new Integer(newPiid));
                // FIXME here the player needs to be put in a state that is the same as if it
                //   had been ducked as it starts. At the moment, this works already for linked
                //   players, as is the case in gapless playback.
            }
        }
        return newPiid;
    }
@@ -131,10 +122,11 @@ public final class PlaybackActivityMonitor
        final boolean change;
        synchronized(mPlayerLock) {
            final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
            if (apc == null) {
                return;
            }
            if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
                // FIXME SoundPool not ready for state reporting
            if (apc != null
                    && apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL)
            {
                return;
            }
            if (checkConfigurationCaller(piid, apc, binderUid)) {
@@ -144,12 +136,8 @@ public final class PlaybackActivityMonitor
                Log.e(TAG, "Error handling event " + event);
                change = false;
            }
            if (change && event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
                    && mDuckedUids.contains(new Integer(apc.getClientUid()))) {
                if (DEBUG) { Log.v(TAG, "  > playerEvent() piid=" + piid + " must be ducked"); }
                if (!mDuckedPlayers.contains(new Integer(piid))) {
                    mDuckedPlayers.add(new Integer(piid));
                }
            if (change && event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
                mDuckingManager.checkDuck(apc);
            }
        }
        if (change) {
@@ -163,6 +151,7 @@ public final class PlaybackActivityMonitor
            final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
            if (checkConfigurationCaller(piid, apc, binderUid)) {
                mPlayers.remove(new Integer(piid));
                mDuckingManager.removeReleased(apc);
            }
        }
    }
@@ -182,10 +171,8 @@ public final class PlaybackActivityMonitor
                conf.dump(pw);
            }
            // ducked players
            pw.println("\n  ducked player piids:");
            for (int piid : mDuckedPlayers) {
                pw.println(" " + piid);
            }
            pw.println("\n  ducked players:");
            mDuckingManager.dump(pw);
            // players muted due to the device ringing or being in a call
            pw.println("\n  muted player piids:");
            for (int piid : mMutedPlayers) {
@@ -274,10 +261,9 @@ public final class PlaybackActivityMonitor

    //=================================================================
    // PlayerFocusEnforcer implementation
    private final ArrayList<Integer> mDuckedPlayers = new ArrayList<Integer>();
    private final ArrayList<Integer> mMutedPlayers = new ArrayList<Integer>();
    // size of 2 for typical cases of double-ducking, not expected to grow beyond that, but can
    private final ArrayList<Integer> mDuckedUids = new ArrayList<Integer>(2);

    private final DuckingManager mDuckingManager = new DuckingManager();

    @Override
    public boolean duckPlayers(FocusRequester winner, FocusRequester loser) {
@@ -286,60 +272,46 @@ public final class PlaybackActivityMonitor
                    winner.getClientUid(), loser.getClientUid()));
        }
        synchronized (mPlayerLock) {
            final Integer loserUid = new Integer(loser.getClientUid());
            if (!mDuckedUids.contains(loserUid)) {
                mDuckedUids.add(loserUid);
            }
            if (mPlayers.isEmpty()) {
                return true;
            }
            final Set<Integer> piidSet = mPlayers.keySet();
            final Iterator<Integer> piidIterator = piidSet.iterator();
            // find which players to duck
            while (piidIterator.hasNext()) {
                final Integer piid = piidIterator.next();
                final AudioPlaybackConfiguration apc = mPlayers.get(piid);
                if (apc == null) {
                    continue;
                }
            // check if this UID needs to be ducked (return false if not), and gather list of
            // eligible players to duck
            final Iterator<AudioPlaybackConfiguration> apcIterator = mPlayers.values().iterator();
            final ArrayList<AudioPlaybackConfiguration> apcsToDuck =
                    new ArrayList<AudioPlaybackConfiguration>();
            while (apcIterator.hasNext()) {
                final AudioPlaybackConfiguration apc = apcIterator.next();
                if (!winner.hasSameUid(apc.getClientUid())
                        && loser.hasSameUid(apc.getClientUid())
                        && apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED)
                {
                    if (mDuckedPlayers.contains(new Integer(piid))) {
                        if (DEBUG) { Log.v(TAG, "player " + piid + " already ducked"); }
                    } else if (MediaFocusControl.ENFORCE_DUCKING
                    if (MediaFocusControl.ENFORCE_DUCKING
                            && MediaFocusControl.ENFORCE_DUCKING_FOR_NEW
                            && loser.getSdkTarget() <= MediaFocusControl.DUCKING_IN_APP_SDK_LEVEL) {
                        // legacy behavior, apps used to be notified when they should be ducking
                        if (DEBUG) { Log.v(TAG, "not ducking player " + piid + ": old SDK"); }
                        if (DEBUG) {Log.v(TAG, "not ducking player " + apc.getPlayerInterfaceId()
                                + ": old SDK"); }
                        return false;
                    } else if (apc.getAudioAttributes().getContentType() ==
                            AudioAttributes.CONTENT_TYPE_SPEECH) {
                        // the player is speaking, ducking will make the speech unintelligible
                        // so let the app handle it instead
                        if (DEBUG) { Log.v(TAG, "not ducking player " + piid + ": SPEECH"); }
                        if (DEBUG) { Log.v(TAG, "not ducking player " + apc.getPlayerInterfaceId()
                                + ": SPEECH"); }
                        return false;
                    } else if (apc.getPlayerType()
                            == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
                        // TODO support ducking of SoundPool players
                        return false;
                    } else {
                        try {
                            Log.v(TAG, "ducking player " + piid);
                            apc.getPlayerProxy().applyVolumeShaper(
                                    DUCK_VSHAPE,
                                    PLAY_CREATE_IF_NEEDED);
                            mDuckedPlayers.add(new Integer(piid));
                        } catch (Exception e) {
                            Log.e(TAG, "Error ducking player " + piid, e);
                            // something went wrong trying to duck, so let the app handle it
                            // instead, it may know things we don't
                            return false;
                        }
                    }
                    apcsToDuck.add(apc);
                }
            }
            // add the players eligible for ducking to the list, and duck them
            // (if apcsToDuck is empty, this will at least mark this uid as ducked, so when
            //  players of the same uid start, they will be ducked by DuckingManager.checkDuck())
            mDuckingManager.duckUid(loser.getClientUid(), apcsToDuck);
        }
        return true;
    }
@@ -348,37 +320,7 @@ public final class PlaybackActivityMonitor
    public void unduckPlayers(FocusRequester winner) {
        if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); }
        synchronized (mPlayerLock) {
            if (mDuckedPlayers.isEmpty()) {
                mDuckedUids.remove(new Integer(winner.getClientUid()));
                return;
            }
            final ArrayList<Integer> playersToRemove =
                    new ArrayList<Integer>(mDuckedPlayers.size());
            for (int piid : mDuckedPlayers) {
                final AudioPlaybackConfiguration apc = mPlayers.get(piid);
                if (apc != null) {
                    if (winner.hasSameUid(apc.getClientUid())) {
                        try {
                            Log.v(TAG, "unducking player " + piid);
                            apc.getPlayerProxy().applyVolumeShaper(
                                    DUCK_ID,
                                    VolumeShaper.Operation.REVERSE);
                        } catch (Exception e) {
                            Log.e(TAG, "Error unducking player " + piid, e);
                        } finally {
                            playersToRemove.add(piid);
                        }
                    }
                } else {
                    // this piid was in the list of ducked players, but wasn't found, discard it
                    Log.v(TAG, "Error unducking player " + piid + ", player not found");
                    playersToRemove.add(piid);
                }
            }
            for (int piid : playersToRemove) {
                mDuckedPlayers.remove(new Integer(piid));
            }
            mDuckedUids.remove(new Integer(winner.getClientUid()));
            mDuckingManager.unduckUid(winner.getClientUid(), mPlayers);
        }
    }

@@ -541,4 +483,118 @@ public final class PlaybackActivityMonitor
            mDispatcherCb.asBinder().unlinkToDeath(this, 0);
        }
    }

    //=================================================================
    // Class to handle ducking related operations for a given UID
    private static final class DuckingManager {
        private final HashMap<Integer, DuckedApp> mDuckers = new HashMap<Integer, DuckedApp>();

        void duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck) {
            if (!mDuckers.containsKey(uid)) {
                mDuckers.put(uid, new DuckedApp(uid));
            }
            final DuckedApp da = mDuckers.get(uid);
            for (AudioPlaybackConfiguration apc : apcsToDuck) {
                da.addDuck(apc);
            }
        }

        void unduckUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) {
            final DuckedApp da = mDuckers.remove(uid);
            if (da == null) {
                return;
            }
            da.removeUnduckAll(players);
        }

        // pre-condition: apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
        void checkDuck(@NonNull AudioPlaybackConfiguration apc) {
            final DuckedApp da = mDuckers.get(apc.getClientUid());
            if (da == null) {
                return;
            }
            // FIXME here the player needs to be put in a state that is the same as if it
            //   had been ducked as it starts. At the moment, this works already for linked
            //   players, as is the case in gapless playback.
            da.addDuck(apc);
        }

        void dump(PrintWriter pw) {
            for (DuckedApp da : mDuckers.values()) {
                da.dump(pw);
            }
        }

        void removeReleased(@NonNull AudioPlaybackConfiguration apc) {
            final DuckedApp da = mDuckers.get(apc.getClientUid());
            if (da == null) {
                return;
            }
            da.removeReleased(apc);
        }

        private static final class DuckedApp {
            private final int mUid;
            private final ArrayList<Integer> mDuckedPlayers = new ArrayList<Integer>();

            DuckedApp(int uid) {
                mUid = uid;
            }

            void dump(PrintWriter pw) {
                pw.print("\t uid:" + mUid + " piids:");
                for (int piid : mDuckedPlayers) {
                    pw.print(" " + piid);
                }
                pw.println("");
            }

            // pre-conditions:
            //  * apc != null
            //  * apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
            void addDuck(@NonNull AudioPlaybackConfiguration apc) {
                final int piid = new Integer(apc.getPlayerInterfaceId());
                if (mDuckedPlayers.contains(piid)) {
                    if (DEBUG) { Log.v(TAG, "player " + piid + " already ducked"); }
                    return;
                }
                try {
                    Log.v(TAG, "ducking player " + apc.getPlayerInterfaceId());
                    apc.getPlayerProxy().applyVolumeShaper(
                            DUCK_VSHAPE,
                            PLAY_CREATE_IF_NEEDED);
                    mDuckedPlayers.add(piid);
                } catch (Exception e) {
                    Log.e(TAG, "Error ducking player " + piid, e);
                }
            }

            void removeUnduckAll(HashMap<Integer, AudioPlaybackConfiguration> players) {
                for (int piid : mDuckedPlayers) {
                    final AudioPlaybackConfiguration apc = players.get(piid);
                    if (apc != null) {
                        try {
                            Log.v(TAG, "unducking player " + piid);
                            apc.getPlayerProxy().applyVolumeShaper(
                                    DUCK_ID,
                                    VolumeShaper.Operation.REVERSE);
                        } catch (Exception e) {
                            Log.e(TAG, "Error unducking player " + piid, e);
                        }
                    } else {
                        // this piid was in the list of ducked players, but wasn't found
                        if (DEBUG) {
                            Log.v(TAG, "Error unducking player " + piid + ", player not found for"
                                    + " uid " + mUid);
                        }
                    }
                }
                mDuckedPlayers.clear();
            }

            void removeReleased(@NonNull AudioPlaybackConfiguration apc) {
                mDuckedPlayers.remove(new Integer(apc.getPlayerInterfaceId()));
            }
        }
    }
}