diff options
6 files changed, 363 insertions, 60 deletions
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java index 5f6f4b125710..dde98b466ef7 100644 --- a/services/core/java/com/android/server/audio/FadeOutManager.java +++ b/services/core/java/com/android/server/audio/FadeOutManager.java @@ -21,8 +21,10 @@ import android.media.AudioAttributes; import android.media.AudioManager; import android.media.AudioPlaybackConfiguration; import android.media.VolumeShaper; -import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.server.utils.EventLogger; @@ -38,7 +40,7 @@ public final class FadeOutManager { public static final String TAG = "AudioService.FadeOutManager"; /** duration of the fade out curve */ - /*package*/ static final long FADE_OUT_DURATION_MS = 2000; + private static final long FADE_OUT_DURATION_MS = 2000; /** * delay after which a faded out player will be faded back in. This will be heard by the user * only in the case of unmuting players that didn't respect audio focus and didn't stop/pause @@ -46,7 +48,7 @@ public final class FadeOutManager { * This is the amount of time between the app being notified of * the focus loss (when its muted by the fade out), and the time fade in (to unmute) starts */ - /*package*/ static final long DELAY_FADE_IN_OFFENDERS_MS = 2000; + private static final long DELAY_FADE_IN_OFFENDERS_MS = 2000; private static final boolean DEBUG = PlaybackActivityMonitor.DEBUG; @@ -81,6 +83,14 @@ public final class FadeOutManager { private static final VolumeShaper.Operation PLAY_SKIP_RAMP = new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build(); + private final Object mLock = new Object(); + + /** + * Map of uid (key) to faded out apps (value) + */ + @GuardedBy("mLock") + private final SparseArray<FadedOutApp> mUidToFadedAppsMap = new SparseArray<>(); + // TODO explore whether a shorter fade out would be a better UX instead of not fading out at all // (legacy behavior) @@ -90,15 +100,18 @@ public final class FadeOutManager { * @param requester the parameters for the focus request * @return true if there can be a fade out over the requester starting to play */ - static boolean canCauseFadeOut(@NonNull FocusRequester requester, - @NonNull FocusRequester loser) { + boolean canCauseFadeOut(@NonNull FocusRequester requester, @NonNull FocusRequester loser) { if (requester.getAudioAttributes().getContentType() == AudioAttributes.CONTENT_TYPE_SPEECH) { - if (DEBUG) { Log.i(TAG, "not fading out: new focus is for speech"); } + if (DEBUG) { + Slog.i(TAG, "not fading out: new focus is for speech"); + } return false; } if ((loser.getGrantFlags() & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0) { - if (DEBUG) { Log.i(TAG, "not fading out: loser has PAUSES_ON_DUCKABLE_LOSS"); } + if (DEBUG) { + Slog.i(TAG, "not fading out: loser has PAUSES_ON_DUCKABLE_LOSS"); + } return false; } @@ -110,29 +123,36 @@ public final class FadeOutManager { * @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) { + boolean canBeFadedOut(@NonNull AudioPlaybackConfiguration apc) { if (ArrayUtils.contains(UNFADEABLE_PLAYER_TYPES, apc.getPlayerType())) { - if (DEBUG) { Log.i(TAG, "not fading: player type:" + apc.getPlayerType()); } + if (DEBUG) { + Slog.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:" + Slog.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()); + Slog.i(TAG, "not fading: usage:" + apc.getAudioAttributes().getUsage()); } return false; } return true; } - static long getFadeOutDurationOnFocusLossMillis(AudioAttributes aa) { + /** + * Get the duration to fade-out after losing audio focus + * @param aa The {@link android.media.AudioAttributes} of the player + * @return duration in milliseconds + */ + long getFadeOutDurationOnFocusLossMillis(@NonNull AudioAttributes aa) { if (ArrayUtils.contains(UNFADEABLE_CONTENT_TYPES, aa.getContentType())) { return 0; } @@ -143,18 +163,24 @@ public final class FadeOutManager { } /** - * Map of uid (key) to faded out apps (value) + * Get the delay to fade-in the offending players that do not stop after losing audio focus + * @param aa The {@link android.media.AudioAttributes} + * @return duration in milliseconds */ - private final HashMap<Integer, FadedOutApp> mFadedApps = new HashMap<Integer, FadedOutApp>(); + long getFadeInDelayForOffendersMillis(@NonNull AudioAttributes aa) { + return DELAY_FADE_IN_OFFENDERS_MS; + } - 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*/); + void fadeOutUid(int uid, ArrayList<AudioPlaybackConfiguration> players) { + Slog.i(TAG, "fadeOutUid() uid:" + uid); + synchronized (mLock) { + if (!mUidToFadedAppsMap.contains(uid)) { + mUidToFadedAppsMap.put(uid, new FadedOutApp(uid)); + } + final FadedOutApp fa = mUidToFadedAppsMap.get(uid); + for (AudioPlaybackConfiguration apc : players) { + fa.addFade(apc, /* skipRamp= */ false); + } } } @@ -163,49 +189,69 @@ public final class FadeOutManager { * @param uid the uid for the app to unfade out * @param players map of current available players (so we can get an APC from piid) */ - synchronized void unfadeOutUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) { - Log.i(TAG, "unfadeOutUid() uid:" + uid); - final FadedOutApp fa = mFadedApps.remove(uid); - if (fa == null) { - return; + void unfadeOutUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) { + Slog.i(TAG, "unfadeOutUid() uid:" + uid); + synchronized (mLock) { + final FadedOutApp fa = mUidToFadedAppsMap.get(uid); + if (fa == null) { + return; + } + mUidToFadedAppsMap.remove(uid); + fa.removeUnfadeAll(players); } - fa.removeUnfadeAll(players); } // pre-condition: apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED // see {@link PlaybackActivityMonitor#playerEvent} - synchronized void checkFade(@NonNull AudioPlaybackConfiguration apc) { + void checkFade(@NonNull AudioPlaybackConfiguration apc) { if (DEBUG) { - Log.v(TAG, "checkFade() player piid:" + Slog.v(TAG, "checkFade() player piid:" + apc.getPlayerInterfaceId() + " uid:" + apc.getClientUid()); } - final FadedOutApp fa = mFadedApps.get(apc.getClientUid()); - if (fa == null) { - return; + synchronized (mLock) { + final FadedOutApp fa = mUidToFadedAppsMap.get(apc.getClientUid()); + if (fa == null) { + return; + } + fa.addFade(apc, /* skipRamp= */ true); } - 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) { + void removeReleased(@NonNull AudioPlaybackConfiguration apc) { final int uid = apc.getClientUid(); if (DEBUG) { - Log.v(TAG, "removedReleased() player piid: " + Slog.v(TAG, "removedReleased() player piid: " + apc.getPlayerInterfaceId() + " uid:" + uid); } - final FadedOutApp fa = mFadedApps.get(uid); - if (fa == null) { - return; + synchronized (mLock) { + final FadedOutApp fa = mUidToFadedAppsMap.get(uid); + if (fa == null) { + return; + } + fa.removeReleased(apc); } - fa.removeReleased(apc); } - synchronized void dump(PrintWriter pw) { - for (FadedOutApp da : mFadedApps.values()) { - da.dump(pw); + /** + * Check if uid is currently faded out + * @param uid Client id + * @return true if uid is currently faded out. Othwerwise, false. + */ + boolean isUidFadedOut(int uid) { + synchronized (mLock) { + return mUidToFadedAppsMap.contains(uid); + } + } + + void dump(PrintWriter pw) { + synchronized (mLock) { + for (int index = 0; index < mUidToFadedAppsMap.size(); index++) { + mUidToFadedAppsMap.valueAt(index).dump(pw); + } } } @@ -240,7 +286,7 @@ public final class FadeOutManager { final int piid = new Integer(apc.getPlayerInterfaceId()); if (mFadedPlayers.contains(piid)) { if (DEBUG) { - Log.v(TAG, "player piid:" + piid + " already faded out"); + Slog.v(TAG, "player piid:" + piid + " already faded out"); } return; } @@ -252,7 +298,7 @@ public final class FadeOutManager { skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED); mFadedPlayers.add(piid); } catch (Exception e) { - Log.e(TAG, "Error fading out player piid:" + piid + Slog.e(TAG, "Error fading out player piid:" + piid + " uid:" + apc.getClientUid(), e); } } @@ -269,12 +315,12 @@ public final class FadeOutManager { FADEOUT_VSHAPE, VolumeShaper.Operation.REVERSE); } catch (Exception e) { - Log.e(TAG, "Error unfading out player piid:" + piid + " uid:" + mUid, e); + Slog.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 + Slog.v(TAG, "Error unfading out player piid:" + piid + ", player not found for uid " + mUid); } } diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java index 010d5f41bc7d..00c04ff12c89 100644 --- a/services/core/java/com/android/server/audio/FocusRequester.java +++ b/services/core/java/com/android/server/audio/FocusRequester.java @@ -495,7 +495,8 @@ public class FocusRequester { // will be dispatched later, it is now in limbo mode mFocusLossFadeLimbo = true; mFocusController.postDelayedLossAfterFade(this, - FadeOutManager.FADE_OUT_DURATION_MS); + mFocusController.getFadeOutDurationOnFocusLossMillis( + this.getAudioAttributes())); return true; } } diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index 65f6c9b8d459..58f5d5e21cf0 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -94,7 +94,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { private final Context mContext; private final AppOpsManager mAppOps; - private PlayerFocusEnforcer mFocusEnforcer; // never null + private final @NonNull PlayerFocusEnforcer mFocusEnforcer; private boolean mMultiAudioFocusEnabled = false; private boolean mRingOrCallActive = false; @@ -128,7 +128,8 @@ public class MediaFocusControl implements PlayerFocusEnforcer { * @return the fade out duration in ms */ public long getFocusFadeOutDurationForTest() { - return FadeOutManager.FADE_OUT_DURATION_MS; + return getFadeOutDurationMillis( + new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build()); } /** @@ -137,7 +138,8 @@ public class MediaFocusControl implements PlayerFocusEnforcer { * @return the time gap after a fade out completion on focus loss, and fade in start in ms */ public long getFocusUnmuteDelayAfterFadeOutForTest() { - return FadeOutManager.DELAY_FADE_IN_OFFENDERS_MS; + return getFadeInDelayForOffendersMillis( + new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build()); } //================================================================= @@ -178,6 +180,21 @@ public class MediaFocusControl implements PlayerFocusEnforcer { mFocusEnforcer.forgetUid(uid); } + @Override + public long getFadeOutDurationMillis(@NonNull AudioAttributes aa) { + if (aa == null) { + return 0; + } + return mFocusEnforcer.getFadeOutDurationMillis(aa); + } + + @Override + public long getFadeInDelayForOffendersMillis(@NonNull AudioAttributes aa) { + if (aa == null) { + return 0; + } + return mFocusEnforcer.getFadeInDelayForOffendersMillis(aa); + } //========================================================================================== // AudioFocus //========================================================================================== @@ -1401,7 +1418,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { if (!ENFORCE_FADEOUT_FOR_FOCUS_LOSS) { return 0; } - return FadeOutManager.getFadeOutDurationOnFocusLossMillis(aa); + return getFadeOutDurationMillis(aa); } private void dumpMultiAudioFocus(PrintWriter pw) { @@ -1423,14 +1440,14 @@ public class MediaFocusControl implements PlayerFocusEnforcer { Log.v(TAG, "postDelayedLossAfterFade loser=" + focusLoser.getPackageName()); } mFocusHandler.sendMessageDelayed( - mFocusHandler.obtainMessage(MSG_L_FOCUS_LOSS_AFTER_FADE, focusLoser), - FadeOutManager.FADE_OUT_DURATION_MS); + mFocusHandler.obtainMessage(MSG_L_FOCUS_LOSS_AFTER_FADE, focusLoser), delayMs); } - private void postForgetUidLater(int uid) { + private void postForgetUidLater(FocusRequester focusRequester) { mFocusHandler.sendMessageDelayed( - mFocusHandler.obtainMessage(MSL_L_FORGET_UID, new ForgetFadeUidInfo(uid)), - FadeOutManager.DELAY_FADE_IN_OFFENDERS_MS); + mFocusHandler.obtainMessage(MSL_L_FORGET_UID, + new ForgetFadeUidInfo(focusRequester.getClientUid())), + getFadeInDelayForOffendersMillis(focusRequester.getAudioAttributes())); } //================================================================= @@ -1466,7 +1483,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { if (loser.isInFocusLossLimbo()) { loser.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS); loser.release(); - postForgetUidLater(loser.getClientUid()); + postForgetUidLater(loser); } } break; diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 54fa6fbc3bfd..26e1248c087e 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -914,7 +914,7 @@ public final class PlaybackActivityMonitor if (DEBUG) { Log.v(TAG, "no players to fade out"); } return false; } - if (!FadeOutManager.canCauseFadeOut(winner, loser)) { + if (!mFadingManager.canCauseFadeOut(winner, loser)) { return false; } // check if this UID needs to be faded out (return false if not), and gather list of @@ -928,7 +928,7 @@ public final class PlaybackActivityMonitor && loser.hasSameUid(apc.getClientUid()) && apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) { - if (!FadeOutManager.canBeFadedOut(apc)) { + if (!mFadingManager.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() @@ -960,6 +960,16 @@ public final class PlaybackActivityMonitor mDuckingManager.unduckUid(uid, players); } + @Override + public long getFadeOutDurationMillis(@NonNull AudioAttributes aa) { + return mFadingManager.getFadeOutDurationOnFocusLossMillis(aa); + } + + @Override + public long getFadeInDelayForOffendersMillis(@NonNull AudioAttributes aa) { + return mFadingManager.getFadeInDelayForOffendersMillis(aa); + } + //================================================================= // Track playback activity listeners diff --git a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java index fb72ac282e8d..f1d42f3571a9 100644 --- a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java +++ b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java @@ -17,6 +17,7 @@ package com.android.server.audio; import android.annotation.NonNull; +import android.media.AudioAttributes; public interface PlayerFocusEnforcer { @@ -64,4 +65,18 @@ public interface PlayerFocusEnforcer { * @param uid */ void forgetUid(int uid); + + /** + * Get the fade out duration currently active for the given usage + * @param aa The {@link android.media.AudioAttributes} + * @return fade out duration in milliseconds + */ + long getFadeOutDurationMillis(@NonNull AudioAttributes aa); + + /** + * Returns the delay to fade-in the offending players + * @param aa The {@link android.media.AudioAttributes} + * @return delay in milliseconds + */ + long getFadeInDelayForOffendersMillis(@NonNull AudioAttributes aa); }
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java new file mode 100644 index 000000000000..fa94821d4ff2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2023 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 static android.media.AudioAttributes.USAGE_MEDIA; +import static android.media.AudioAttributes.USAGE_GAME; +import static android.media.AudioAttributes.CONTENT_TYPE_SPEECH; +import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO; +import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK; +import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN; + +import android.content.Context; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.AudioPlaybackConfiguration; +import android.media.PlayerBase; +import android.os.Parcel; + +import androidx.test.core.app.ApplicationProvider; + +import com.google.common.truth.Expect; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.HashMap; + +@RunWith(JUnit4.class) +public final class FadeOutManagerTest { + private static final int TEST_UID_SYSTEM = 1000; + private static final int TEST_UID_USER = 10100; + private static final int TEST_SESSION_ID = 10; + private static final int TEST_PIID_1 = 101; + private static final int TEST_PIID_2 = 102; + private static final int TEST_PIID_3 = 103; + private static final int TEST_PID = 10101; + + private static final AudioAttributes TEST_MEDIA_AUDIO_ATTRIBUTE = + new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build(); + private static final AudioAttributes TEST_GAME_AUDIO_ATTRIBUTE = + new AudioAttributes.Builder().setUsage(USAGE_GAME).build(); + private static final AudioAttributes TEST_SPEECH_AUDIO_ATTRIBUTE = + new AudioAttributes.Builder().setContentType(CONTENT_TYPE_SPEECH).build(); + private FadeOutManager mFadeOutManager; + private Context mContext; + + @Mock + PlayerBase.PlayerIdCard mMockPlayerIdCard; + @Mock + AudioPlaybackConfiguration mMockPlaybackConfiguration; + + @Rule + public final Expect expect = Expect.create(); + + @Before + public void setUp() { + mFadeOutManager = new FadeOutManager(); + mContext = ApplicationProvider.getApplicationContext(); + } + + @Test + public void testCanCauseFadeOut_forFaders_returnsTrue() { + FocusRequester winner = createFocusRequester(TEST_MEDIA_AUDIO_ATTRIBUTE, "winning-client", + "unit-test", TEST_UID_USER, + AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS); + FocusRequester loser = createFocusRequester(TEST_SPEECH_AUDIO_ATTRIBUTE, "losing-client", + "unit-test", TEST_UID_USER, AudioManager.AUDIOFOCUS_FLAG_TEST); + + expect.withMessage("Can cause fade out").that(mFadeOutManager.canCauseFadeOut( + winner, loser)).isTrue(); + } + + @Test + public void testCanCauseFadeOut_forUnfaderSpeechAttribute_returnsFalse() { + FocusRequester winner = createFocusRequester(TEST_SPEECH_AUDIO_ATTRIBUTE, "winning-client", + "unit-test", TEST_UID_USER, AudioManager.AUDIOFOCUS_FLAG_TEST); + FocusRequester loser = createFocusRequester(TEST_SPEECH_AUDIO_ATTRIBUTE, "losing-client", + "unit-test", TEST_UID_USER, AudioManager.AUDIOFOCUS_FLAG_TEST); + + expect.withMessage("Can cause fade out for speech attribute") + .that(mFadeOutManager.canCauseFadeOut(winner, loser)).isFalse(); + } + + @Test + public void testCanCauseFadeOut_forUnfaderFlag_returnsFalse() { + FocusRequester winner = createFocusRequester(TEST_SPEECH_AUDIO_ATTRIBUTE, "winning-client", + "unit-test", TEST_UID_USER, AudioManager.AUDIOFOCUS_FLAG_TEST); + FocusRequester loser = createFocusRequester(TEST_MEDIA_AUDIO_ATTRIBUTE, "losing-client", + "unit-test", TEST_UID_USER, + AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS); + + expect.withMessage("Can cause fade out for flag pause on duckable loss") + .that(mFadeOutManager.canCauseFadeOut(winner, loser)).isFalse(); + } + + @Test + public void testCanBeFadedOut_forFadableConfig_returnsTrue() { + AudioPlaybackConfiguration apcMedia = createAudioPlaybackConfiguration( + TEST_MEDIA_AUDIO_ATTRIBUTE, TEST_UID_USER, TEST_PIID_1, TEST_PID, + PLAYER_TYPE_JAM_AUDIOTRACK); + AudioPlaybackConfiguration apcGame = createAudioPlaybackConfiguration( + TEST_GAME_AUDIO_ATTRIBUTE, TEST_UID_USER, TEST_PIID_2, TEST_PID, + PLAYER_TYPE_UNKNOWN); + + expect.withMessage("Fadability for media audio attribute") + .that(mFadeOutManager.canBeFadedOut(apcMedia)).isTrue(); + expect.withMessage("Fadability for game audio attribute") + .that(mFadeOutManager.canBeFadedOut(apcGame)).isTrue(); + } + + @Test + public void testCanBeFadedOut_forUnFadableConfig_returnsFalse() { + AudioPlaybackConfiguration apcSpeech = createAudioPlaybackConfiguration( + TEST_SPEECH_AUDIO_ATTRIBUTE, TEST_UID_USER, TEST_PIID_1, TEST_PID, + PLAYER_TYPE_JAM_AUDIOTRACK); + AudioPlaybackConfiguration apcAAudio = createAudioPlaybackConfiguration( + TEST_GAME_AUDIO_ATTRIBUTE, TEST_UID_USER, TEST_PIID_2, TEST_PID, + PLAYER_TYPE_AAUDIO); + + expect.withMessage("Fadability for speech audio attribute") + .that(mFadeOutManager.canBeFadedOut(apcSpeech)).isFalse(); + expect.withMessage("Fadability for AAudio player type") + .that(mFadeOutManager.canBeFadedOut(apcAAudio)).isFalse(); + } + + @Test + public void testFadeOutUid_getsFadedOut_isFadedReturnsTrue() { + final ArrayList<AudioPlaybackConfiguration> apcsToFadeOut = + new ArrayList<AudioPlaybackConfiguration>(); + apcsToFadeOut.add(createAudioPlaybackConfiguration( + TEST_MEDIA_AUDIO_ATTRIBUTE, TEST_UID_USER, TEST_PIID_1, TEST_PID, + PLAYER_TYPE_JAM_AUDIOTRACK)); + + mFadeOutManager.fadeOutUid(TEST_UID_USER, apcsToFadeOut); + + expect.withMessage("Fade out state for uid") + .that(mFadeOutManager.isUidFadedOut(TEST_UID_USER)).isTrue(); + } + + @Test + public void testUnfadeOutUid_getsUnfaded_isFadedReturnsFalse() { + final ArrayList<AudioPlaybackConfiguration> apcsToUnfade = + new ArrayList<AudioPlaybackConfiguration>(); + final ArrayList<AudioPlaybackConfiguration> apcsToKeepfaded = + new ArrayList<AudioPlaybackConfiguration>(); + AudioPlaybackConfiguration apcMediaPiid1 = createAudioPlaybackConfiguration( + TEST_MEDIA_AUDIO_ATTRIBUTE, TEST_UID_USER, TEST_PIID_1, TEST_PID, + PLAYER_TYPE_JAM_AUDIOTRACK); + AudioPlaybackConfiguration apcGamePiid2 = createAudioPlaybackConfiguration( + TEST_GAME_AUDIO_ATTRIBUTE, TEST_UID_USER, TEST_PIID_2, TEST_PID, + PLAYER_TYPE_JAM_AUDIOTRACK); + AudioPlaybackConfiguration apcMediaPiid3 = createAudioPlaybackConfiguration( + TEST_MEDIA_AUDIO_ATTRIBUTE, TEST_UID_SYSTEM, TEST_PIID_3, TEST_PID, + PLAYER_TYPE_JAM_AUDIOTRACK); + HashMap<Integer, AudioPlaybackConfiguration> mapPiidToApcs = new HashMap<>(); + apcsToUnfade.add(apcMediaPiid1); + apcsToUnfade.add(apcGamePiid2); + apcsToKeepfaded.add(apcMediaPiid3); + mFadeOutManager.fadeOutUid(TEST_UID_USER, apcsToUnfade); + mFadeOutManager.fadeOutUid(TEST_UID_SYSTEM, apcsToKeepfaded); + mapPiidToApcs.put(TEST_PIID_1, apcMediaPiid1); + mapPiidToApcs.put(TEST_PIID_2, apcGamePiid2); + + mFadeOutManager.unfadeOutUid(TEST_UID_USER, mapPiidToApcs); + + expect.withMessage("Fade out state after unfading for uid") + .that(mFadeOutManager.isUidFadedOut(TEST_UID_USER)).isFalse(); + expect.withMessage("Fade out state for uid") + .that(mFadeOutManager.isUidFadedOut(TEST_UID_SYSTEM)).isTrue(); + + } + + private FocusRequester createFocusRequester(AudioAttributes aa, String clientId, + String packageName, int uid, int flags) { + MediaFocusControl mfc = new MediaFocusControl(mContext, null); + return new FocusRequester(aa, AudioManager.AUDIOFOCUS_GAIN, flags, null, null, clientId, + null, packageName, uid, mfc, 1); + } + + private PlayerBase.PlayerIdCard createPlayerIdCard(AudioAttributes aa, int playerType) { + + Parcel p = Parcel.obtain(); + p.writeInt(playerType); + aa.writeToParcel(p, 0); + p.writeStrongInterface(null); + p.writeInt(TEST_SESSION_ID); + p.setDataPosition(0); + return PlayerBase.PlayerIdCard.CREATOR.createFromParcel(p); + } + + private AudioPlaybackConfiguration createAudioPlaybackConfiguration(AudioAttributes aa, int uid, + int piid, int pid, int playerType) { + return new AudioPlaybackConfiguration(createPlayerIdCard(aa, playerType), piid, uid, pid); + } +} |