diff options
5 files changed, 489 insertions, 18 deletions
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java new file mode 100644 index 000000000000..9f0a2baea91d --- /dev/null +++ b/services/core/java/com/android/server/audio/FadeOutManager.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.audio; + +import android.annotation.NonNull; +import android.media.AudioAttributes; +import android.media.AudioPlaybackConfiguration; +import android.media.VolumeShaper; +import android.util.Log; + +import com.android.internal.util.ArrayUtils; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Class to handle fading out players + */ +public final class FadeOutManager { + + public static final String TAG = "AudioService.FadeOutManager"; + + /*package*/ static final long FADE_OUT_DURATION_MS = 2500; + + private static final boolean DEBUG = PlaybackActivityMonitor.DEBUG; + + private static final VolumeShaper.Configuration FADEOUT_VSHAPE = + new VolumeShaper.Configuration.Builder() + .setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID) + .setCurve(new float[]{0.f, 1.0f} /* times */, + new float[]{1.f, 0.0f} /* volumes */) + .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME) + .setDuration(FADE_OUT_DURATION_MS) + .build(); + private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED = + new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY) + .createIfNeeded() + .build(); + + private static final int[] UNFADEABLE_PLAYER_TYPES = { + AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO, + AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL, + }; + + private static final int[] UNFADEABLE_CONTENT_TYPES = { + AudioAttributes.CONTENT_TYPE_SPEECH, + }; + + private static final int[] FADEABLE_USAGES = { + AudioAttributes.USAGE_GAME, + AudioAttributes.USAGE_MEDIA, + }; + + // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp + private static final VolumeShaper.Operation PLAY_SKIP_RAMP = + new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build(); + + /** + * Evaluates whether the player associated with this configuration can and should be faded out + * @param apc the configuration of the player + * @return true if player type and AudioAttributes are compatible with fade out + */ + static boolean canBeFadedOut(@NonNull AudioPlaybackConfiguration apc) { + if (ArrayUtils.contains(UNFADEABLE_PLAYER_TYPES, apc.getPlayerType())) { + if (DEBUG) { Log.i(TAG, "not fading: player type:" + apc.getPlayerType()); } + return false; + } + if (ArrayUtils.contains(UNFADEABLE_CONTENT_TYPES, + apc.getAudioAttributes().getContentType())) { + if (DEBUG) { + Log.i(TAG, "not fading: content type:" + + apc.getAudioAttributes().getContentType()); + } + return false; + } + if (!ArrayUtils.contains(FADEABLE_USAGES, apc.getAudioAttributes().getUsage())) { + if (DEBUG) { + Log.i(TAG, "not fading: usage:" + apc.getAudioAttributes().getUsage()); + } + return false; + } + return true; + } + + /** + * Map of uid (key) to faded out apps (value) + */ + private final HashMap<Integer, FadedOutApp> mFadedApps = new HashMap<Integer, FadedOutApp>(); + + synchronized void fadeOutUid(int uid, ArrayList<AudioPlaybackConfiguration> players) { + Log.i(TAG, "fadeOutUid() uid:" + uid); + if (!mFadedApps.containsKey(uid)) { + mFadedApps.put(uid, new FadedOutApp(uid)); + } + final FadedOutApp fa = mFadedApps.get(uid); + for (AudioPlaybackConfiguration apc : players) { + fa.addFade(apc, false /*skipRamp*/); + } + } + + synchronized void unfadeOutUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) { + Log.i(TAG, "unfadeOutUid() uid:" + uid); + final FadedOutApp fa = mFadedApps.remove(uid); + if (fa == null) { + return; + } + 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) { + if (DEBUG) { + Log.v(TAG, "checkFade() player piid:" + + apc.getPlayerInterfaceId() + " uid:" + apc.getClientUid()); + } + final FadedOutApp fa = mFadedApps.get(apc.getClientUid()); + if (fa == null) { + return; + } + fa.addFade(apc, true); + } + + /** + * Remove the player from the list of faded out players because it has been released + * @param apc the released player + */ + synchronized void removeReleased(@NonNull AudioPlaybackConfiguration apc) { + final int uid = apc.getClientUid(); + if (DEBUG) { + Log.v(TAG, "removedReleased() player piid: " + + apc.getPlayerInterfaceId() + " uid:" + uid); + } + final FadedOutApp fa = mFadedApps.get(uid); + if (fa == null) { + return; + } + fa.removeReleased(apc); + } + + synchronized void dump(PrintWriter pw) { + for (FadedOutApp da : mFadedApps.values()) { + da.dump(pw); + } + } + + //========================================================================= + /** + * Class to group players from a common app, that are faded out. + */ + private static final class FadedOutApp { + private final int mUid; + private final ArrayList<Integer> mFadedPlayers = new ArrayList<Integer>(); + + FadedOutApp(int uid) { + mUid = uid; + } + + void dump(PrintWriter pw) { + pw.print("\t uid:" + mUid + " piids:"); + for (int piid : mFadedPlayers) { + pw.print(" " + piid); + } + pw.println(""); + } + + /** + * Add this player to the list of faded out players and apply the fade + * @param apc a config that satisfies + * apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED + * @param skipRamp true if the player should be directly into the end of ramp state. + * This value would for instance be false when adding players at the start of a fade. + */ + void addFade(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) { + final int piid = new Integer(apc.getPlayerInterfaceId()); + if (mFadedPlayers.contains(piid)) { + if (DEBUG) { + Log.v(TAG, "player piid:" + piid + " already faded out"); + } + return; + } + try { + PlaybackActivityMonitor.sEventLogger.log( + (new PlaybackActivityMonitor.FadeOutEvent(apc, skipRamp)).printLog(TAG)); + apc.getPlayerProxy().applyVolumeShaper( + FADEOUT_VSHAPE, + skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED); + mFadedPlayers.add(piid); + } catch (Exception e) { + Log.e(TAG, "Error fading out player piid:" + piid + + " uid:" + apc.getClientUid(), e); + } + } + + void removeUnfadeAll(HashMap<Integer, AudioPlaybackConfiguration> players) { + for (int piid : mFadedPlayers) { + final AudioPlaybackConfiguration apc = players.get(piid); + if (apc != null) { + try { + PlaybackActivityMonitor.sEventLogger.log( + (new AudioEventLogger.StringEvent("unfading out piid:" + + piid)).printLog(TAG)); + apc.getPlayerProxy().applyVolumeShaper( + FADEOUT_VSHAPE, + VolumeShaper.Operation.REVERSE); + } catch (Exception e) { + Log.e(TAG, "Error unfading out player piid:" + piid + " uid:" + mUid, e); + } + } else { + // this piid was in the list of faded players, but wasn't found + if (DEBUG) { + Log.v(TAG, "Error unfading out player piid:" + piid + + ", player not found for uid " + mUid); + } + } + } + mFadedPlayers.clear(); + } + + void removeReleased(@NonNull AudioPlaybackConfiguration apc) { + mFadedPlayers.remove(new Integer(apc.getPlayerInterfaceId())); + } + } +} diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java index b6d647289ed8..cc60fe1200b1 100644 --- a/services/core/java/com/android/server/audio/FocusRequester.java +++ b/services/core/java/com/android/server/audio/FocusRequester.java @@ -70,6 +70,11 @@ public class FocusRequester { */ private boolean mFocusLossWasNotified; /** + * whether this focus owner has already lost focus, but is being faded out until focus loss + * dispatch occurs. It's in "limbo" mode: has lost focus but not released yet until notified + */ + boolean mFocusLossFadeLimbo; + /** * the audio attributes associated with the focus request */ private final @NonNull AudioAttributes mAttributes; @@ -102,6 +107,7 @@ public class FocusRequester { mGrantFlags = grantFlags; mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE; mFocusLossWasNotified = true; + mFocusLossFadeLimbo = false; mFocusController = ctlr; mSdkTarget = sdk; } @@ -115,6 +121,7 @@ public class FocusRequester { mFocusGainRequest = afi.getGainRequest(); mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE; mFocusLossWasNotified = true; + mFocusLossFadeLimbo = false; mGrantFlags = afi.getFlags(); mSdkTarget = afi.getSdkTarget(); @@ -132,6 +139,13 @@ public class FocusRequester { return ((mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0); } + /** + * @return true if the focus requester is scheduled to receive a focus loss + */ + boolean isInFocusLossLimbo() { + return mFocusLossFadeLimbo; + } + boolean hasSameBinder(IBinder ib) { return (mSourceRef != null) && mSourceRef.equals(ib); } @@ -231,11 +245,21 @@ public class FocusRequester { + " -- flags: " + flagsToString(mGrantFlags) + " -- loss: " + focusLossToString() + " -- notified: " + mFocusLossWasNotified + + " -- limbo" + mFocusLossFadeLimbo + " -- uid: " + mCallingUid + " -- attr: " + mAttributes + " -- sdk:" + mSdkTarget); } + /** + * Clear all references, except for instances in "loss limbo" due to the current fade out + * for which there will be an attempt to be clear after the loss has been notified + */ + void maybeRelease() { + if (!mFocusLossFadeLimbo) { + release(); + } + } void release() { final IBinder srcRef = mSourceRef; @@ -315,6 +339,7 @@ public class FocusRequester { void handleFocusGain(int focusGain) { try { mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE; + mFocusLossFadeLimbo = false; mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(), AudioManager.AUDIOFOCUS_REQUEST_GRANTED); final IAudioFocusDispatcher fd = mFocusDispatcher; @@ -327,7 +352,7 @@ public class FocusRequester { fd.dispatchAudioFocusChange(focusGain, mClientId); } } - mFocusController.unduckPlayers(this); + mFocusController.restoreVShapedPlayers(this); } catch (android.os.RemoteException e) { Log.e(TAG, "Failure to signal gain of audio focus due to: ", e); } @@ -336,7 +361,7 @@ public class FocusRequester { @GuardedBy("MediaFocusControl.mAudioFocusLock") void handleFocusGainFromRequest(int focusRequestResult) { if (focusRequestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { - mFocusController.unduckPlayers(this); + mFocusController.restoreVShapedPlayers(this); } } @@ -375,7 +400,7 @@ public class FocusRequester { if (handled) { if (DEBUG) { Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived) - + " to " + mClientId + ", ducking implemented by framework"); + + " to " + mClientId + ", response handled by framework"); } mFocusController.notifyExtPolicyFocusLoss_syncAf( toAudioFocusInfo(), false /* wasDispatched */); @@ -435,8 +460,27 @@ public class FocusRequester { return false; } - return mFocusController.duckPlayers(frWinner, this, forceDuck); + return mFocusController.duckPlayers(frWinner, /*loser*/ this, forceDuck); + } + + if (focusLoss == AudioManager.AUDIOFOCUS_LOSS) { + if (!MediaFocusControl.ENFORCE_FADEOUT_FOR_FOCUS_LOSS) { + return false; + } + + // candidate for fade-out before a receiving a loss + boolean playersAreFaded = mFocusController.fadeOutPlayers(frWinner, /* loser */ this); + if (playersAreFaded) { + // active players are being faded out, delay the dispatch of focus loss + // mark this instance as being faded so it's not released yet as the focus loss + // will be dispatched later, it is now in limbo mode + mFocusLossFadeLimbo = true; + mFocusController.postDelayedLossAfterFade(this, + FadeOutManager.FADE_OUT_DURATION_MS); + return true; + } } + return false; } diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index b1633b01df90..1dcfdaec44ec 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -30,7 +30,10 @@ import android.media.MediaMetrics; import android.media.audiopolicy.IAudioPolicyCallback; import android.os.Binder; import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; +import android.os.Message; import android.os.RemoteException; import android.provider.Settings; import android.util.Log; @@ -80,6 +83,12 @@ public class MediaFocusControl implements PlayerFocusEnforcer { */ static final boolean ENFORCE_MUTING_FOR_RING_OR_CALL = true; + /** + * set to true so the framework enforces fading out apps that lose audio focus in a + * non-transient way. + */ + static final boolean ENFORCE_FADEOUT_FOR_FOCUS_LOSS = true; + private final Context mContext; private final AppOpsManager mAppOps; private PlayerFocusEnforcer mFocusEnforcer; // never null @@ -98,6 +107,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { final ContentResolver cr = mContext.getContentResolver(); mMultiAudioFocusEnabled = Settings.System.getIntForUser(cr, Settings.System.MULTI_AUDIO_FOCUS_ENABLED, 0, cr.getUserId()) != 0; + initFocusThreading(); } protected void dump(PrintWriter pw) { @@ -119,8 +129,8 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } @Override - public void unduckPlayers(@NonNull FocusRequester winner) { - mFocusEnforcer.unduckPlayers(winner); + public void restoreVShapedPlayers(@NonNull FocusRequester winner) { + mFocusEnforcer.restoreVShapedPlayers(winner); } @Override @@ -133,6 +143,16 @@ public class MediaFocusControl implements PlayerFocusEnforcer { mFocusEnforcer.unmutePlayersForCall(); } + @Override + public boolean fadeOutPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser) { + return mFocusEnforcer.fadeOutPlayers(winner, loser); + } + + @Override + public void forgetUid(int uid) { + mFocusEnforcer.forgetUid(uid); + } + //========================================================================================== // AudioFocus //========================================================================================== @@ -294,7 +314,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { { //Log.i(TAG, " removeFocusStackEntry() removing top of stack"); FocusRequester fr = mFocusStack.pop(); - fr.release(); + fr.maybeRelease(); if (notifyFocusFollowers) { abandonSource = fr.toAudioFocusInfo(); } @@ -318,7 +338,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { abandonSource = fr.toAudioFocusInfo(); } // stack entry not used anymore, clear references - fr.release(); + fr.maybeRelease(); } } } @@ -1134,4 +1154,57 @@ public class MediaFocusControl implements PlayerFocusEnforcer { pw.println("------------------------------"); } } + + //================================================================= + // Async focus events + void postDelayedLossAfterFade(FocusRequester focusLoser, long delayMs) { + if (DEBUG) { + Log.v(TAG, "postDelayedLossAfterFade loser=" + focusLoser.getPackageName()); + } + mFocusHandler.sendMessageDelayed( + mFocusHandler.obtainMessage(MSG_L_FOCUS_LOSS_AFTER_FADE, focusLoser), + FadeOutManager.FADE_OUT_DURATION_MS); + } + //================================================================= + // Message handling + private Handler mFocusHandler; + private HandlerThread mFocusThread; + + /** + * dispatch a focus loss after an app has been faded out. Focus loser is to be released + * after dispatch as it has already left the stack + * args: + * msg.obj: the audio focus loser + * type:FocusRequester + */ + private static final int MSG_L_FOCUS_LOSS_AFTER_FADE = 1; + + private void initFocusThreading() { + mFocusThread = new HandlerThread(TAG); + mFocusThread.start(); + mFocusHandler = new Handler(mFocusThread.getLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_L_FOCUS_LOSS_AFTER_FADE: + if (DEBUG) { + Log.d(TAG, "MSG_L_FOCUS_LOSS_AFTER_FADE loser=" + + ((FocusRequester) msg.obj).getPackageName()); + } + synchronized (mAudioFocusLock) { + final FocusRequester loser = (FocusRequester) msg.obj; + if (loser.isInFocusLossLimbo()) { + loser.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS); + loser.release(); + mFocusEnforcer.forgetUid(loser.getClientUid()); + } + } + break; + default: + break; + } + } + }; + + } } diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 8af1b5be1517..47c91e6c23dd 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -51,8 +51,9 @@ public final class PlaybackActivityMonitor public static final String TAG = "AudioService.PlaybackActivityMonitor"; - private static final boolean DEBUG = false; - private static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1; + /*package*/ static final boolean DEBUG = false; + /*package*/ static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1; + /*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2; private static final VolumeShaper.Configuration DUCK_VSHAPE = new VolumeShaper.Configuration.Builder() @@ -298,6 +299,7 @@ public final class PlaybackActivityMonitor } if (change && event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) { mDuckingManager.checkDuck(apc); + mFadingManager.checkFade(apc); } } if (change) { @@ -320,6 +322,7 @@ public final class PlaybackActivityMonitor "releasing player piid:" + piid)); mPlayers.remove(new Integer(piid)); mDuckingManager.removeReleased(apc); + mFadingManager.removeReleased(apc); checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED); change = apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED, AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID); @@ -442,6 +445,9 @@ public final class PlaybackActivityMonitor // ducked players pw.println("\n ducked players piids:"); mDuckingManager.dump(pw); + // faded out players + pw.println("\n faded out players piids:"); + mFadingManager.dump(pw); // players muted due to the device ringing or being in a call pw.print("\n muted player piids:"); for (int piid : mMutedPlayers) { @@ -606,10 +612,11 @@ public final class PlaybackActivityMonitor } @Override - public void unduckPlayers(@NonNull FocusRequester winner) { + public void restoreVShapedPlayers(@NonNull FocusRequester winner) { if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); } synchronized (mPlayerLock) { mDuckingManager.unduckUid(winner.getClientUid(), mPlayers); + mFadingManager.unfadeOutUid(winner.getClientUid(), mPlayers); } } @@ -678,6 +685,67 @@ public final class PlaybackActivityMonitor } } + private final FadeOutManager mFadingManager = new FadeOutManager(); + + /** + * + * @param winner the new non-transient focus owner + * @param loser the previous focus owner + * @return true if there are players being faded out + */ + @Override + public boolean fadeOutPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser) { + if (DEBUG) { + Log.v(TAG, "fadeOutPlayers: winner=" + winner.getPackageName() + + " loser=" + loser.getPackageName()); + } + boolean loserHasActivePlayers = false; + + // find which players to fade out + synchronized (mPlayerLock) { + if (mPlayers.isEmpty()) { + return false; + } + // check if this UID needs to be faded out (return false if not), and gather list of + // eligible players to fade out + final Iterator<AudioPlaybackConfiguration> apcIterator = mPlayers.values().iterator(); + final ArrayList<AudioPlaybackConfiguration> apcsToFadeOut = + 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 (!FadeOutManager.canBeFadedOut(apc)) { + // the player is not eligible to be faded out, bail + Log.v(TAG, "not fading out player " + apc.getPlayerInterfaceId() + + " uid:" + apc.getClientUid() + " pid:" + apc.getClientPid() + + " type:" + + AudioPlaybackConfiguration.toLogFriendlyPlayerType( + apc.getPlayerType()) + + " attr:" + apc.getAudioAttributes()); + return false; + } + loserHasActivePlayers = true; + apcsToFadeOut.add(apc); + } + } + //### + //mDuckingManager.duckUid(loser.getClientUid(), apcsToFadeOut); + if (loserHasActivePlayers) { + mFadingManager.fadeOutUid(loser.getClientUid(), apcsToFadeOut); + } + } + + return loserHasActivePlayers; + } + + @Override + public void forgetUid(int uid) { + mFadingManager.forgetUid(uid); + } + //================================================================= // Track playback activity listeners @@ -964,13 +1032,15 @@ public final class PlaybackActivityMonitor } } - private static final class DuckEvent extends AudioEventLogger.Event { + private abstract static class VolumeShaperEvent extends AudioEventLogger.Event { private final int mPlayerIId; private final boolean mSkipRamp; private final int mClientUid; private final int mClientPid; - DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) { + abstract String getVSAction(); + + VolumeShaperEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) { mPlayerIId = apc.getPlayerInterfaceId(); mSkipRamp = skipRamp; mClientUid = apc.getClientUid(); @@ -979,12 +1049,34 @@ public final class PlaybackActivityMonitor @Override public String eventToString() { - return new StringBuilder("ducking player piid:").append(mPlayerIId) + return new StringBuilder(getVSAction()).append(" player piid:").append(mPlayerIId) .append(" uid/pid:").append(mClientUid).append("/").append(mClientPid) .append(" skip ramp:").append(mSkipRamp).toString(); } } + static final class DuckEvent extends VolumeShaperEvent { + @Override + String getVSAction() { + return "ducking"; + } + + DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) { + super(apc, skipRamp); + } + } + + static final class FadeOutEvent extends VolumeShaperEvent { + @Override + String getVSAction() { + return "fading out"; + } + + FadeOutEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) { + super(apc, skipRamp); + } + } + private static final class AudioAttrEvent extends AudioEventLogger.Event { private final int mPlayerIId; private final AudioAttributes mPlayerAttr; @@ -1000,6 +1092,6 @@ public final class PlaybackActivityMonitor } } - private static final AudioEventLogger sEventLogger = new AudioEventLogger(100, + static final AudioEventLogger sEventLogger = new AudioEventLogger(100, "playback activity as reported through PlayerBase"); } diff --git a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java index 89e7b7828b15..fb72ac282e8d 100644 --- a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java +++ b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java @@ -31,11 +31,12 @@ public interface PlayerFocusEnforcer { boolean forceDuck); /** - * Unduck the players that had been ducked with - * {@link #duckPlayers(FocusRequester, FocusRequester, boolean)} + * Restore the initial state of any players that had had a volume ramp applied as the result + * of a duck or fade out through {@link #duckPlayers(FocusRequester, FocusRequester, boolean)} + * or {@link #fadeOutPlayers(FocusRequester, FocusRequester)} * @param winner */ - void unduckPlayers(@NonNull FocusRequester winner); + void restoreVShapedPlayers(@NonNull FocusRequester winner); /** * Mute players at the beginning of a call @@ -47,4 +48,20 @@ public interface PlayerFocusEnforcer { * Unmute players at the end of a call */ void unmutePlayersForCall(); + + /** + * Fade out whatever is still playing after the non-transient focus change + * @param winner the new non-transient focus owner + * @param loser the previous focus owner + * @return true if there were any active players for the loser that qualified for being + * faded out (because of audio attributes, or player types), and as such were faded + * out. + */ + boolean fadeOutPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser); + + /** + * Mark this UID as no longer playing a role in focus enforcement + * @param uid + */ + void forgetUid(int uid); }
\ No newline at end of file |