Merge "Audio focus: fade out active playbacks on FOCUS_LOSS" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
4c38e34d86
245
services/core/java/com/android/server/audio/FadeOutManager.java
Normal file
245
services/core/java/com/android/server/audio/FadeOutManager.java
Normal 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user