Merge "Audio focus: fade out active playbacks on FOCUS_LOSS" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-03-06 02:53:39 +00:00
committed by Android (Google) Code Review
5 changed files with 489 additions and 18 deletions

View File

@@ -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()));
}
}
}

View File

@@ -69,6 +69,11 @@ public class FocusRequester {
* whether this focus owner listener was notified when it lost focus
*/
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
*/
@@ -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;
}

View File

@@ -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;
}
}
};
}
}

View File

@@ -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");
}

View File

@@ -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);
}