summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Raj Goparaju <rajgoparaju@google.com> 2023-12-19 18:03:58 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-12-19 18:03:58 +0000
commitcd5f79d201fe1305be869433ef29d406a42bfb91 (patch)
treef1ef0ef298bd68bfeb435b65d055498cf30fe63a
parent68fb1e93a1cc90c01822b716d585f181658fba19 (diff)
parent0428580bb44e666807b80c4ab5ce254605c4d7c5 (diff)
Merge changes from topic "feature_audioCrossover" into main
* changes: Support dispatch audio focus with fade manager config Add API to set fade manager configuration Adapt fade manager configurations Remove fade manager aconfig from media solutions repo
-rw-r--r--AconfigFlags.bp5
-rw-r--r--core/api/system-current.txt67
-rw-r--r--media/java/android/media/AudioManager.java66
-rw-r--r--media/java/android/media/FadeManagerConfiguration.java15
-rw-r--r--media/java/android/media/IAudioService.aidl21
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicy.java108
-rw-r--r--media/java/android/media/flags/fade_manager_configuration.aconfig8
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java96
-rw-r--r--services/core/java/com/android/server/audio/FadeConfigurations.java427
-rw-r--r--services/core/java/com/android/server/audio/FadeOutManager.java219
-rw-r--r--services/core/java/com/android/server/audio/FocusRequester.java28
-rw-r--r--services/core/java/com/android/server/audio/MediaFocusControl.java106
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java64
-rw-r--r--services/core/java/com/android/server/audio/PlayerFocusEnforcer.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java135
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java13
17 files changed, 1249 insertions, 138 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 705a4df6c30f..efd85788574e 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -430,10 +430,7 @@ java_aconfig_library {
aconfig_declarations {
name: "com.android.media.flags.bettertogether-aconfig",
package: "com.android.media.flags",
- srcs: [
- "media/java/android/media/flags/media_better_together.aconfig",
- "media/java/android/media/flags/fade_manager_configuration.aconfig",
- ],
+ srcs: ["media/java/android/media/flags/media_better_together.aconfig"],
}
java_aconfig_library {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 63b142926653..51e61e6239bb 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6472,6 +6472,7 @@ package android.media {
method public void clearAudioServerStateCallback();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int dispatchAudioFocusChangeWithFade(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy, @NonNull java.util.List<android.media.AudioFocusInfo>, @Nullable android.media.FadeManagerConfiguration);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getActiveAssistantServicesUids();
method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getAssistantServicesUids();
@@ -6671,6 +6672,69 @@ package android.media {
field public static final int CONTENT_ID_NONE = 0; // 0x0
}
+ @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") public final class FadeManagerConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.media.AudioAttributes> getAudioAttributesWithVolumeShaperConfigs();
+ method public long getFadeInDelayForOffenders();
+ method public long getFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method public long getFadeInDurationForUsage(int);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeInVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeInVolumeShaperConfigForUsage(int);
+ method public long getFadeOutDurationForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method public long getFadeOutDurationForUsage(int);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeOutVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeOutVolumeShaperConfigForUsage(int);
+ method public int getFadeState();
+ method @NonNull public java.util.List<java.lang.Integer> getFadeableUsages();
+ method @NonNull public java.util.List<android.media.AudioAttributes> getUnfadeableAudioAttributes();
+ method @NonNull public java.util.List<java.lang.Integer> getUnfadeableContentTypes();
+ method @NonNull public java.util.List<java.lang.Integer> getUnfadeablePlayerTypes();
+ method @NonNull public java.util.List<java.lang.Integer> getUnfadeableUids();
+ method public boolean isAudioAttributesUnfadeable(@NonNull android.media.AudioAttributes);
+ method public boolean isContentTypeUnfadeable(int);
+ method public boolean isFadeEnabled();
+ method public boolean isPlayerTypeUnfadeable(int);
+ method public boolean isUidUnfadeable(int);
+ method public boolean isUsageFadeable(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.FadeManagerConfiguration> CREATOR;
+ field public static final long DURATION_NOT_SET = 0L; // 0x0L
+ field public static final int FADE_STATE_DISABLED = 0; // 0x0
+ field public static final int FADE_STATE_ENABLED_AUTO = 2; // 0x2
+ field public static final int FADE_STATE_ENABLED_DEFAULT = 1; // 0x1
+ field public static final String TAG = "FadeManagerConfiguration";
+ field public static final int VOLUME_SHAPER_SYSTEM_FADE_ID = 2; // 0x2
+ }
+
+ public static final class FadeManagerConfiguration.Builder {
+ ctor public FadeManagerConfiguration.Builder();
+ ctor public FadeManagerConfiguration.Builder(long, long);
+ ctor public FadeManagerConfiguration.Builder(@NonNull android.media.FadeManagerConfiguration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addFadeableUsage(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableContentType(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableUid(int);
+ method @NonNull public android.media.FadeManagerConfiguration build();
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearFadeableUsage(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableContentType(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableUid(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDelayForOffenders(long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForUsage(int, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInVolumeShaperConfigForUsage(int, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutDurationForAudioAttributes(@NonNull android.media.AudioAttributes, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutDurationForUsage(int, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutVolumeShaperConfigForUsage(int, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeState(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeableUsages(@NonNull java.util.List<java.lang.Integer>);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableAudioAttributes(@NonNull java.util.List<android.media.AudioAttributes>);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableContentTypes(@NonNull java.util.List<java.lang.Integer>);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableUids(@NonNull java.util.List<java.lang.Integer>);
+ }
+
public class HwAudioSource {
method public boolean isPlaying();
method public void start();
@@ -6889,15 +6953,18 @@ package android.media.audiopolicy {
public class AudioPolicy {
method public int attachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>);
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int clearFadeManagerConfigurationForFocusLoss();
method public android.media.AudioRecord createAudioRecordSink(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException;
method public android.media.AudioTrack createAudioTrackSource(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException;
method public int detachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>);
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public android.media.FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss();
method public int getFocusDuckingBehavior();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioFocusInfo> getFocusStack();
method public int getStatus();
method public boolean removeUidDeviceAffinity(int);
method public boolean removeUserIdDeviceAffinity(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean sendFocusLoss(@NonNull android.media.AudioFocusInfo) throws java.lang.IllegalStateException;
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int setFadeManagerConfigurationForFocusLoss(@NonNull android.media.FadeManagerConfiguration);
method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setRegistration(String);
method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 3dfd5726455d..a5a69f987113 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -22,6 +22,7 @@ import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.media.audio.Flags.automaticBtDeviceType;
import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.Manifest;
import android.annotation.CallbackExecutor;
@@ -5120,6 +5121,71 @@ public class AudioManager {
}
/**
+ * Notifies an application with a focus listener of gain or loss of audio focus
+ *
+ * <p>This is similar to {@link #dispatchAudioFocusChange(AudioFocusInfo, int, AudioPolicy)} but
+ * with additional functionality of fade. The players of the application with audio focus
+ * change, provided they meet the active {@link FadeManagerConfiguration} requirements, are
+ * faded before dispatching the callback to the application. For example, players of the
+ * application losing audio focus will be faded out, whereas players of the application gaining
+ * audio focus will be faded in, if needed.
+ *
+ * <p>The applicability of fade is decided against the supplied active {@link AudioFocusInfo}.
+ * This list cannot be {@code null}. The list can be empty if no other active
+ * {@link AudioFocusInfo} available at the time of the dispatch.
+ *
+ * <p>The {@link FadeManagerConfiguration} supplied here is prioritized over existing fade
+ * configurations. If none supplied, either the {@link FadeManagerConfiguration} set through
+ * {@link AudioPolicy} or the default will be used to determine the fade properties.
+ *
+ * <p>This method can only be used by owners of an {@link AudioPolicy} configured with
+ * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to true.
+ *
+ * @param afi the recipient of the focus change, that has previously requested audio focus, and
+ * that was received by the {@code AudioPolicy} through
+ * {@link AudioPolicy.AudioPolicyFocusListener#onAudioFocusRequest(AudioFocusInfo, int)}
+ * @param focusChange one of focus gain types ({@link #AUDIOFOCUS_GAIN},
+ * {@link #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or
+ * {@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE})
+ * or one of the focus loss types ({@link AudioManager#AUDIOFOCUS_LOSS},
+ * {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT},
+ * or {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}).
+ * <br>For the focus gain, the change type should be the same as the app requested
+ * @param ap a valid registered {@link AudioPolicy} configured as a focus policy.
+ * @param otherActiveAfis active {@link AudioFocusInfo} that are granted audio focus at the time
+ * of dispatch
+ * @param transientFadeMgrConfig {@link FadeManagerConfiguration} that will be used for fading
+ * players resulting from this dispatch. This is a transient configuration that is only
+ * valid for this focus change and shall be discarded after processing this request.
+ * @return {@link #AUDIOFOCUS_REQUEST_FAILED} if the focus client didn't have a listener or if
+ * there was an error sending the request, or {@link #AUDIOFOCUS_REQUEST_GRANTED} if the
+ * dispatch was successfully sent, or {@link #AUDIOFOCUS_REQUEST_DELAYED} if
+ * the request was successful but the dispatch of focus change was delayed due to a fade
+ * operation.
+ * @throws NullPointerException if the {@link AudioFocusInfo} or {@link AudioPolicy} or list of
+ * other active {@link AudioFocusInfo} are {@code null}.
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int dispatchAudioFocusChangeWithFade(@NonNull AudioFocusInfo afi, int focusChange,
+ @NonNull AudioPolicy ap, @NonNull List<AudioFocusInfo> otherActiveAfis,
+ @Nullable FadeManagerConfiguration transientFadeMgrConfig) {
+ Objects.requireNonNull(afi, "AudioFocusInfo cannot be null");
+ Objects.requireNonNull(ap, "AudioPolicy cannot be null");
+ Objects.requireNonNull(otherActiveAfis, "Other active AudioFocusInfo list cannot be null");
+
+ IAudioService service = getService();
+ try {
+ return service.dispatchFocusChangeWithFade(afi, focusChange, ap.cb(), otherActiveAfis,
+ transientFadeMgrConfig);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @hide
* Used internally by telephony package to abandon audio focus, typically after a call or
* when ringing ends and the call is rejected or not answered.
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index 337d4b0a916c..40b0e3e03ef6 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -16,12 +16,13 @@
package android.media;
-import static com.android.media.flags.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
@@ -93,11 +94,9 @@ import java.util.Objects;
* Helps with recreating a new instance from another to simply change/add on top of the
* existing ones</li>
* </ul>
- * TODO(b/304835727): Convert into system API so that it can be set through AudioPolicy
- *
* @hide
*/
-
+@SystemApi
@FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
public final class FadeManagerConfiguration implements Parcelable {
@@ -523,6 +522,7 @@ public final class FadeManagerConfiguration implements Parcelable {
*
* @param fadeState one of the fade state in {@link FadeStateEnum}
* @return human-readable string
+ * @hide
*/
@NonNull
public static String fadeStateToString(@FadeStateEnum int fadeState) {
@@ -712,7 +712,8 @@ public final class FadeManagerConfiguration implements Parcelable {
*
* <p><b>Notes:</b>
* <ul>
- * <li>When fade state is set to enabled, the builder expects at least one valid usage to be
+ * <li>When fade state is set to {@link #FADE_STATE_ENABLED_DEFAULT} or
+ * {@link #FADE_STATE_ENABLED_AUTO}, the builder expects at least one valid usage to be
* set/added. Failure to do so will result in an exception during {@link #build()}</li>
* <li>Every usage added to the fadeable list should have corresponding volume shaper
* configs defined. This can be achieved by setting either the duration or volume shaper
@@ -720,8 +721,8 @@ public final class FadeManagerConfiguration implements Parcelable {
* {@link #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)}</li>
* <li> It is recommended to set volume shaper configurations individually for fade out and
* fade in</li>
- * <li>For any incomplete volume shaper configs a volume shaper configuration will be
- * created using either the default fade durations or the ones provided as part of the
+ * <li>For any incomplete volume shaper configurations, a volume shaper configuration will
+ * be created using either the default fade durations or the ones provided as part of the
* {@link #Builder(long, long)}</li>
* <li>Additional volume shaper configs can also configured for a given usage
* with additional attributes like content-type in order to achieve finer fade controls.
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index a52f0b08330d..5c268d4ab652 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -28,6 +28,7 @@ import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
import android.media.BluetoothProfileConnectionInfo;
+import android.media.FadeManagerConfiguration;
import android.media.IAudioDeviceVolumeDispatcher;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioModeDispatcher;
@@ -398,6 +399,14 @@ interface IAudioService {
int dispatchFocusChange(in AudioFocusInfo afi, in int focusChange,
in IAudioPolicyCallback pcb);
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ int dispatchFocusChangeWithFade(in AudioFocusInfo afi,
+ in int focusChange,
+ in IAudioPolicyCallback pcb,
+ in List<AudioFocusInfo> otherActiveAfis,
+ in FadeManagerConfiguration transientFadeMgrConfig);
+
oneway void playerHasOpPlayAudio(in int piid, in boolean hasOpPlayAudio);
@EnforcePermission("BLUETOOTH_STACK")
@@ -754,4 +763,16 @@ interface IAudioService {
oneway void removeLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo);
PersistableBundle getLoudnessParams(int piid, in LoudnessCodecInfo codecInfo);
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ int setFadeManagerConfigurationForFocusLoss(in FadeManagerConfiguration fmcForFocusLoss);
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ int clearFadeManagerConfigurationForFocusLoss();
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss();
}
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index e16849811b9d..b85decc74ff8 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -16,6 +16,8 @@
package android.media.audiopolicy;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -34,6 +36,7 @@ import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
+import android.media.FadeManagerConfiguration;
import android.media.IAudioService;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
@@ -49,6 +52,7 @@ import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -612,6 +616,110 @@ public class AudioPolicy {
return mRegistrationId;
}
+ /**
+ * Sets a custom {@link FadeManagerConfiguration} to handle fade cycle of players during
+ * {@link android.media.AudioManager#AUDIOFOCUS_LOSS}
+ *
+ * @param fmcForFocusLoss custom {@link FadeManagerConfiguration}
+ * @return {@link AudioManager#SUCCESS} if the update was successful,
+ * {@link AudioManager#ERROR} otherwise
+ * @throws IllegalStateException if the audio policy is not registered
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @SystemApi
+ public int setFadeManagerConfigurationForFocusLoss(
+ @NonNull FadeManagerConfiguration fmcForFocusLoss) {
+ Objects.requireNonNull(fmcForFocusLoss,
+ "FadeManagerConfiguration for focus loss cannot be null");
+
+ IAudioService service = getService();
+ synchronized (mLock) {
+ Preconditions.checkState(isAudioPolicyRegisteredLocked(),
+ "Cannot set FadeManagerConfiguration with unregistered AudioPolicy");
+
+ try {
+ return service.setFadeManagerConfigurationForFocusLoss(fmcForFocusLoss);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception for setFadeManagerConfigurationForFocusLoss:",
+ e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Clear the current {@link FadeManagerConfiguration} set to handle fade cycles of players
+ * during {@link android.media.AudioManager#AUDIOFOCUS_LOSS}
+ *
+ * <p>In the absence of custom {@link FadeManagerConfiguration}, the default configurations will
+ * be used to handle fade cycles during audio focus loss.
+ *
+ * @return {@link AudioManager#SUCCESS} if the update was successful,
+ * {@link AudioManager#ERROR} otherwise
+ * @throws IllegalStateException if the audio policy is not registered
+ * @see #setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @SystemApi
+ public int clearFadeManagerConfigurationForFocusLoss() {
+ IAudioService service = getService();
+ synchronized (mLock) {
+ Preconditions.checkState(isAudioPolicyRegisteredLocked(),
+ "Cannot clear FadeManagerConfiguration from unregistered AudioPolicy");
+
+ try {
+ return service.clearFadeManagerConfigurationForFocusLoss();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception for "
+ + "clearFadeManagerConfigurationForFocusLoss:", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Get the current fade manager configuration used for fade operations during
+ * {@link android.media.AudioManager#AUDIOFOCUS_LOSS}
+ *
+ * <p>If no custom {@link FadeManagerConfiguration} is set, the default configuration currently
+ * active will be returned.
+ *
+ * @return the active {@link FadeManagerConfiguration} used during audio focus loss
+ * @throws IllegalStateException if the audio policy is not registered
+ * @see #setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)
+ * @see #clearFadeManagerConfigurationForFocusLoss()
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @SystemApi
+ @NonNull
+ public FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss() {
+ IAudioService service = getService();
+ synchronized (mLock) {
+ Preconditions.checkState(isAudioPolicyRegisteredLocked(),
+ "Cannot get FadeManagerConfiguration from unregistered AudioPolicy");
+
+ try {
+ return service.getFadeManagerConfigurationForFocusLoss();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception for getFadeManagerConfigurationForFocusLoss:",
+ e);
+ throw e.rethrowFromSystemServer();
+
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isAudioPolicyRegisteredLocked() {
+ return mStatus == POLICY_STATUS_REGISTERED;
+ }
+
private boolean policyReadyToUse() {
synchronized (mLock) {
if (mStatus != POLICY_STATUS_REGISTERED) {
diff --git a/media/java/android/media/flags/fade_manager_configuration.aconfig b/media/java/android/media/flags/fade_manager_configuration.aconfig
deleted file mode 100644
index 100e2235a7a8..000000000000
--- a/media/java/android/media/flags/fade_manager_configuration.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.media.flags"
-
-flag {
- namespace: "media_solutions"
- name: "enable_fade_manager_configuration"
- description: "Enable Fade Manager Configuration support to determine fade properties"
- bug: "307354764"
-} \ No newline at end of file
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
index fb6bd489d5d0..f105ae9cc33e 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
@@ -16,7 +16,7 @@
package com.android.audiopolicytest;
-import static com.android.media.flags.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import static org.junit.Assert.assertThrows;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 56ae2bfcb38f..ea791b775125 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -33,6 +33,7 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static android.media.AudioManager.STREAM_SYSTEM;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.INVALID_UID;
import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
@@ -116,6 +117,7 @@ import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.AudioTrack;
import android.media.BluetoothProfileConnectionInfo;
+import android.media.FadeManagerConfiguration;
import android.media.IAudioDeviceVolumeDispatcher;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioModeDispatcher;
@@ -4513,6 +4515,8 @@ public class AudioService extends IAudioService.Stub
+ bluetoothMacAddressAnonymization());
pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
+ disablePrescaleAbsoluteVolume());
+ pw.println("\tandroid.media.audiopolicy.enableFadeManagerConfiguration:"
+ + enableFadeManagerConfiguration());
}
private void dumpAudioMode(PrintWriter pw) {
@@ -12614,6 +12618,47 @@ public class AudioService extends IAudioService.Stub
}
/**
+ * see {@link AudioPolicy#setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int setFadeManagerConfigurationForFocusLoss(
+ @NonNull FadeManagerConfiguration fmcForFocusLoss) {
+ super.setFadeManagerConfigurationForFocusLoss_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+ Objects.requireNonNull(fmcForFocusLoss,
+ "Fade manager config for focus loss cannot be null");
+ validateFadeManagerConfiguration(fmcForFocusLoss);
+
+ return mPlaybackMonitor.setFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS,
+ fmcForFocusLoss);
+ }
+
+ /**
+ * see {@link AudioPolicy#clearFadeManagerConfigurationForFocusLoss()}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int clearFadeManagerConfigurationForFocusLoss() {
+ super.clearFadeManagerConfigurationForFocusLoss_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+
+ return mPlaybackMonitor.clearFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS);
+ }
+
+ /**
+ * see {@link AudioPolicy#getFadeManagerConfigurationForFocusLoss()}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss() {
+ super.getFadeManagerConfigurationForFocusLoss_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+
+ return mPlaybackMonitor.getFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS);
+ }
+
+ /**
* @see AudioManager#getHalVersion
*/
public @Nullable AudioHalVersionInfo getHalVersion() {
@@ -12814,6 +12859,19 @@ public class AudioService extends IAudioService.Stub
}
}
+ private void ensureFadeManagerConfigIsEnabled() {
+ Preconditions.checkState(enableFadeManagerConfiguration(),
+ "Fade manager configuration not supported");
+ }
+
+ private void validateFadeManagerConfiguration(FadeManagerConfiguration fmc) {
+ // validate permission of audio attributes
+ List<AudioAttributes> attrs = fmc.getAudioAttributesWithVolumeShaperConfigs();
+ for (int index = 0; index < attrs.size(); index++) {
+ validateAudioAttributesUsage(attrs.get(index));
+ }
+ }
+
//======================
// Audio policy callbacks from AudioSystem for dynamic policies
//======================
@@ -13114,6 +13172,7 @@ public class AudioService extends IAudioService.Stub
+ "could not link to " + projection + " binder death", e);
}
}
+
int status = connectMixes();
if (status != AudioSystem.SUCCESS) {
release();
@@ -13471,6 +13530,43 @@ public class AudioService extends IAudioService.Stub
}
}
+ /**
+ * see {@link AudioManager#dispatchAudioFocusChangeWithFade(AudioFocusInfo, int, AudioPolicy,
+ * List, FadeManagerConfiguration)}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int dispatchFocusChangeWithFade(AudioFocusInfo afi, int focusChange,
+ IAudioPolicyCallback pcb, List<AudioFocusInfo> otherActiveAfis,
+ FadeManagerConfiguration transientFadeMgrConfig) {
+ super.dispatchFocusChangeWithFade_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+ Objects.requireNonNull(afi, "AudioFocusInfo cannot be null");
+ Objects.requireNonNull(pcb, "AudioPolicy callback cannot be null");
+ Objects.requireNonNull(otherActiveAfis,
+ "Other active AudioFocusInfo list cannot be null");
+ if (transientFadeMgrConfig != null) {
+ validateFadeManagerConfiguration(transientFadeMgrConfig);
+ }
+
+ synchronized (mAudioPolicies) {
+ Preconditions.checkState(mAudioPolicies.containsKey(pcb.asBinder()),
+ "Unregistered AudioPolicy for focus dispatch with fade");
+
+ // set the transient fade manager config to be used for handling this focus change
+ if (transientFadeMgrConfig != null) {
+ mPlaybackMonitor.setTransientFadeManagerConfiguration(focusChange,
+ transientFadeMgrConfig);
+ }
+ int status = mMediaFocusControl.dispatchFocusChangeWithFade(afi, focusChange,
+ otherActiveAfis);
+
+ if (transientFadeMgrConfig != null) {
+ mPlaybackMonitor.clearTransientFadeManagerConfiguration(focusChange);
+ }
+ return status;
+ }
+ }
//======================
// Audioserver state dispatch
diff --git a/services/core/java/com/android/server/audio/FadeConfigurations.java b/services/core/java/com/android/server/audio/FadeConfigurations.java
index 2e27c7697e82..37ecf0bdcc7a 100644
--- a/services/core/java/com/android/server/audio/FadeConfigurations.java
+++ b/services/core/java/com/android/server/audio/FadeConfigurations.java
@@ -16,13 +16,22 @@
package com.android.server.audio;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.media.AudioAttributes;
+import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
+import android.media.FadeManagerConfiguration;
import android.media.VolumeShaper;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Class to encapsulate configurations used for fading players
@@ -69,51 +78,229 @@ public final class FadeConfigurations {
private static final int INVALID_UID = -1;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mDefaultFadeManagerConfig;
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mUpdatedFadeManagerConfig;
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mTransientFadeManagerConfig;
+ /** active fade manager is one of: transient > updated > default */
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mActiveFadeManagerConfig;
+
+ /**
+ * Sets the custom fade manager configuration
+ *
+ * @param fadeManagerConfig custom fade manager configuration
+ * @return {@link AudioManager#SUCCESS} if setting custom fade manager configuration succeeds
+ * or {@link AudioManager#ERROR} otherwise (example - when fade manager configuration
+ * feature is disabled)
+ */
+ public int setFadeManagerConfiguration(
+ @NonNull FadeManagerConfiguration fadeManagerConfig) {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+
+ synchronized (mLock) {
+ mUpdatedFadeManagerConfig = Objects.requireNonNull(fadeManagerConfig,
+ "Fade manager configuration cannot be null");
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Clears the fade manager configuration that was previously set with
+ * {@link #setFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if previously set fade manager configuration is cleared
+ * or {@link AudioManager#ERROR} otherwise (example, when fade manager configuration feature
+ * is disabled)
+ */
+ public int clearFadeManagerConfiguration() {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+
+ synchronized (mLock) {
+ mUpdatedFadeManagerConfig = null;
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Returns the active fade manager configuration
+ *
+ * @return {@code null} if feature is disabled, or the custom fade manager configuration if set,
+ * or default fade manager configuration if not set.
+ */
+ @Nullable
+ public FadeManagerConfiguration getFadeManagerConfiguration() {
+ if (!enableFadeManagerConfiguration()) {
+ return null;
+ }
+
+ synchronized (mLock) {
+ return mActiveFadeManagerConfig;
+ }
+ }
+
+ /**
+ * Sets the transient fade manager configuration
+ *
+ * @param fadeManagerConfig custom fade manager configuration
+ * @return {@link AudioManager#SUCCESS} if setting custom fade manager configuration succeeds
+ * or {@link AudioManager#ERROR} otherwise (example - when fade manager configuration is
+ * disabled)
+ */
+ public int setTransientFadeManagerConfiguration(
+ @NonNull FadeManagerConfiguration fadeManagerConfig) {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+
+ synchronized (mLock) {
+ mTransientFadeManagerConfig = Objects.requireNonNull(fadeManagerConfig,
+ "Transient FadeManagerConfiguration cannot be null");
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Clears the transient fade manager configuration that was previously set with
+ * {@link #setTransientFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if previously set transient fade manager configuration
+ * is cleared or {@link AudioManager#ERROR} otherwise (example - when fade manager
+ * configuration is disabled)
+ */
+ public int clearTransientFadeManagerConfiguration() {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+ synchronized (mLock) {
+ mTransientFadeManagerConfig = null;
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Query if fade should be enforecd on players
+ *
+ * @return {@code true} if fade is enabled or using default configurations, {@code false}
+ * otherwise.
+ */
+ public boolean isFadeEnabled() {
+ if (!enableFadeManagerConfiguration()) {
+ return true;
+ }
+
+ synchronized (mLock) {
+ return getUpdatedFadeManagerConfigLocked().isFadeEnabled();
+ }
+ }
+
/**
* Query {@link android.media.AudioAttributes.AttributeUsage usages} that are allowed to
* fade
+ *
* @return list of {@link android.media.AudioAttributes.AttributeUsage}
*/
@NonNull
public List<Integer> getFadeableUsages() {
- return DEFAULT_FADEABLE_USAGES;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADEABLE_USAGES;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return an empty list instead
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getFadeableUsages()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Query {@link android.media.AudioAttributes.AttributeContentType content types} that are
* exempted from fade enforcement
+ *
* @return list of {@link android.media.AudioAttributes.AttributeContentType}
*/
@NonNull
public List<Integer> getUnfadeableContentTypes() {
- return DEFAULT_UNFADEABLE_CONTENT_TYPES;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_CONTENT_TYPES;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return an empty list instead
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeableContentTypes()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Query {@link android.media.AudioPlaybackConfiguration.PlayerType player types} that are
* exempted from fade enforcement
+ *
* @return list of {@link android.media.AudioPlaybackConfiguration.PlayerType}
*/
@NonNull
public List<Integer> getUnfadeablePlayerTypes() {
- return DEFAULT_UNFADEABLE_PLAYER_TYPES;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_PLAYER_TYPES;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return an empty list instead
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeablePlayerTypes()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Get the {@link android.media.VolumeShaper.Configuration} configuration to be applied
* for the fade-out
+ *
* @param aa The {@link android.media.AudioAttributes}
* @return {@link android.media.VolumeShaper.Configuration} for the
- * {@link android.media.AudioAttributes.AttributeUsage} or default volume shaper if not
- * configured
+ * {@link android.media.AudioAttributes} or default volume shaper if not configured
*/
@NonNull
public VolumeShaper.Configuration getFadeOutVolumeShaperConfig(@NonNull AudioAttributes aa) {
- return DEFAULT_FADEOUT_VSHAPE;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADEOUT_VSHAPE;
+ }
+ return getOptimalFadeOutVolShaperConfig(aa);
+ }
+
+ /**
+ * Get the {@link android.media.VolumeShaper.Configuration} configuration to be applied for the
+ * fade in
+ *
+ * @param aa The {@link android.media.AudioAttributes}
+ * @return {@link android.media.VolumeShaper.Configuration} for the
+ * {@link android.media.AudioAttributes} or {@code null} otherwise
+ */
+ @Nullable
+ public VolumeShaper.Configuration getFadeInVolumeShaperConfig(@NonNull AudioAttributes aa) {
+ if (!enableFadeManagerConfiguration()) {
+ return null;
+ }
+ return getOptimalFadeInVolShaperConfig(aa);
}
+
/**
* Get the duration to fade out a player of type usage
+ *
* @param aa The {@link android.media.AudioAttributes}
* @return duration in milliseconds for the
* {@link android.media.AudioAttributes} or default duration if not configured
@@ -122,22 +309,73 @@ public final class FadeConfigurations {
if (!isFadeable(aa, INVALID_UID, AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN)) {
return 0;
}
- return DEFAULT_FADE_OUT_DURATION_MS;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADE_OUT_DURATION_MS;
+ }
+ return getOptimalFadeOutDuration(aa);
}
/**
- * Get the delay to fade in offending players that do not stop after losing audio focus.
+ * Get the delay to fade in offending players that do not stop after losing audio focus
+ *
* @param aa The {@link android.media.AudioAttributes}
* @return delay in milliseconds for the
* {@link android.media.AudioAttributes.Attribute} or default delay if not configured
*/
public long getDelayFadeInOffenders(@NonNull AudioAttributes aa) {
- return DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
+ }
+
+ synchronized (mLock) {
+ return getUpdatedFadeManagerConfigLocked().getFadeInDelayForOffenders();
+ }
+ }
+
+ /**
+ * Query {@link android.media.AudioAttributes} that are exempted from fade enforcement
+ *
+ * @return list of {@link android.media.AudioAttributes}
+ */
+ @NonNull
+ public List<AudioAttributes> getUnfadeableAudioAttributes() {
+ // unfadeable audio attributes is only supported with fade manager configurations
+ if (!enableFadeManagerConfiguration()) {
+ return Collections.EMPTY_LIST;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return empty list
+ return fadeManagerConfig.isFadeEnabled()
+ ? fadeManagerConfig.getUnfadeableAudioAttributes() : Collections.EMPTY_LIST;
+ }
+ }
+
+ /**
+ * Query uids that are exempted from fade enforcement
+ *
+ * @return list of uids
+ */
+ @NonNull
+ public List<Integer> getUnfadeableUids() {
+ // unfadeable uids is only supported with fade manager configurations
+ if (!enableFadeManagerConfiguration()) {
+ return Collections.EMPTY_LIST;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return empty list
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeableUids()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Check if it is allowed to fade for the given {@link android.media.AudioAttributes},
- * client uid and {@link android.media.AudioPlaybackConfiguration.PlayerType} config.
+ * client uid and {@link android.media.AudioPlaybackConfiguration.PlayerType} config
+ *
* @param aa The {@link android.media.AudioAttributes}
* @param uid The uid of the client owning the player
* @param playerType The {@link android.media.AudioPlaybackConfiguration.PlayerType}
@@ -145,36 +383,173 @@ public final class FadeConfigurations {
*/
public boolean isFadeable(@NonNull AudioAttributes aa, int uid,
@AudioPlaybackConfiguration.PlayerType int playerType) {
- if (isPlayerTypeUnfadeable(playerType)) {
- if (DEBUG) {
- Slog.i(TAG, "not fadeable: player type:" + playerType);
+ synchronized (mLock) {
+ if (isPlayerTypeUnfadeableLocked(playerType)) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: player type:" + playerType);
+ }
+ return false;
}
- return false;
+ if (isContentTypeUnfadeableLocked(aa.getContentType())) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: content type:" + aa.getContentType());
+ }
+ return false;
+ }
+ if (!isUsageFadeableLocked(aa.getSystemUsage())) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: usage:" + aa.getUsage());
+ }
+ return false;
+ }
+ // new configs using fade manager configuration
+ if (isUnfadeableForFadeMgrConfigLocked(aa, uid)) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ /** Tries to get the fade out volume shaper config closest to the audio attributes */
+ private VolumeShaper.Configuration getOptimalFadeOutVolShaperConfig(AudioAttributes aa) {
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // check if the specific audio attributes has a volume shaper config defined
+ VolumeShaper.Configuration volShaperConfig =
+ fadeManagerConfig.getFadeOutVolumeShaperConfigForAudioAttributes(aa);
+ if (volShaperConfig != null) {
+ return volShaperConfig;
+ }
+
+ // get the volume shaper config for usage
+ // for fadeable usages, this should never return null
+ return fadeManagerConfig.getFadeOutVolumeShaperConfigForUsage(
+ aa.getSystemUsage());
}
- if (isContentTypeUnfadeable(aa.getContentType())) {
+ }
+
+ /** Tries to get the fade in volume shaper config closest to the audio attributes */
+ private VolumeShaper.Configuration getOptimalFadeInVolShaperConfig(AudioAttributes aa) {
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // check if the specific audio attributes has a volume shaper config defined
+ VolumeShaper.Configuration volShaperConfig =
+ fadeManagerConfig.getFadeInVolumeShaperConfigForAudioAttributes(aa);
+ if (volShaperConfig != null) {
+ return volShaperConfig;
+ }
+
+ // get the volume shaper config for usage
+ // for fadeable usages, this should never return null
+ return fadeManagerConfig.getFadeInVolumeShaperConfigForUsage(aa.getSystemUsage());
+ }
+ }
+
+ /** Tries to get the duration closest to the audio attributes */
+ private long getOptimalFadeOutDuration(AudioAttributes aa) {
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // check if specific audio attributes has a duration defined
+ long duration = fadeManagerConfig.getFadeOutDurationForAudioAttributes(aa);
+ if (duration != FadeManagerConfiguration.DURATION_NOT_SET) {
+ return duration;
+ }
+
+ // get the duration for usage
+ // for fadeable usages, this should never return DURATION_NOT_SET
+ return fadeManagerConfig.getFadeOutDurationForUsage(aa.getSystemUsage());
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isUnfadeableForFadeMgrConfigLocked(AudioAttributes aa, int uid) {
+ if (isAudioAttributesUnfadeableLocked(aa)) {
if (DEBUG) {
- Slog.i(TAG, "not fadeable: content type:" + aa.getContentType());
+ Slog.i(TAG, "not fadeable: aa:" + aa);
}
- return false;
+ return true;
}
- if (!isUsageFadeable(aa.getUsage())) {
+ if (isUidUnfadeableLocked(uid)) {
if (DEBUG) {
- Slog.i(TAG, "not fadeable: usage:" + aa.getUsage());
+ Slog.i(TAG, "not fadeable: uid:" + uid);
}
+ return true;
+ }
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ private boolean isUsageFadeableLocked(int usage) {
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADEABLE_USAGES.contains(usage);
+ }
+ return getUpdatedFadeManagerConfigLocked().isUsageFadeable(usage);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isContentTypeUnfadeableLocked(int contentType) {
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_CONTENT_TYPES.contains(contentType);
+ }
+ return getUpdatedFadeManagerConfigLocked().isContentTypeUnfadeable(contentType);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isPlayerTypeUnfadeableLocked(int playerType) {
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_PLAYER_TYPES.contains(playerType);
+ }
+ return getUpdatedFadeManagerConfigLocked().isPlayerTypeUnfadeable(playerType);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isAudioAttributesUnfadeableLocked(AudioAttributes aa) {
+ if (!enableFadeManagerConfiguration()) {
+ // default fade configs do not support unfadeable audio attributes, hence return false
return false;
}
- return true;
+ return getUpdatedFadeManagerConfigLocked().isAudioAttributesUnfadeable(aa);
}
- private boolean isUsageFadeable(int usage) {
- return getFadeableUsages().contains(usage);
+ @GuardedBy("mLock")
+ private boolean isUidUnfadeableLocked(int uid) {
+ if (!enableFadeManagerConfiguration()) {
+ // default fade configs do not support unfadeable uids, hence return false
+ return false;
+ }
+ return getUpdatedFadeManagerConfigLocked().isUidUnfadeable(uid);
}
- private boolean isContentTypeUnfadeable(int contentType) {
- return getUnfadeableContentTypes().contains(contentType);
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration getUpdatedFadeManagerConfigLocked() {
+ if (mActiveFadeManagerConfig == null) {
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return mActiveFadeManagerConfig;
}
- private boolean isPlayerTypeUnfadeable(int playerType) {
- return getUnfadeablePlayerTypes().contains(playerType);
+ /** Priority between fade manager configs: Transient > Updated > Default */
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration getActiveFadeMgrConfigLocked() {
+ // below configs are arranged in the order of priority
+ // configs placed higher have higher priority
+ if (mTransientFadeManagerConfig != null) {
+ return mTransientFadeManagerConfig;
+ }
+
+ if (mUpdatedFadeManagerConfig != null) {
+ return mUpdatedFadeManagerConfig;
+ }
+
+ // default - must be the lowest priority
+ return getDefaultFadeManagerConfigLocked();
+ }
+
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration getDefaultFadeManagerConfigLocked() {
+ if (mDefaultFadeManagerConfig == null) {
+ mDefaultFadeManagerConfig = new FadeManagerConfiguration.Builder().build();
+ }
+ return mDefaultFadeManagerConfig;
}
}
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index 1171f97533c7..2cceb5ab5d2e 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -16,21 +16,24 @@
package com.android.server.audio;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
+import android.media.FadeManagerConfiguration;
import android.media.VolumeShaper;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Objects;
+import java.util.List;
+import java.util.Map;
/**
* Class to handle fading out players
@@ -40,14 +43,6 @@ public final class FadeOutManager {
public static final String TAG = "AS.FadeOutManager";
private static final boolean DEBUG = PlaybackActivityMonitor.DEBUG;
- private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED =
- new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY)
- .createIfNeeded()
- .build();
-
- // 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();
private final Object mLock = new Object();
@@ -57,16 +52,81 @@ public final class FadeOutManager {
@GuardedBy("mLock")
private final SparseArray<FadedOutApp> mUidToFadedAppsMap = new SparseArray<>();
- @GuardedBy("mLock")
- private FadeConfigurations mFadeConfigurations;
+ private final FadeConfigurations mFadeConfigurations = new FadeConfigurations();
+
+ /**
+ * Sets the custom fade manager configuration to be used for player fade out and in
+ *
+ * @param fadeManagerConfig custom fade manager configuration
+ * @return {@link AudioManager#SUCCESS} if setting fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int setFadeManagerConfiguration(FadeManagerConfiguration fadeManagerConfig) {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.setFadeManagerConfiguration(fadeManagerConfig);
+ }
+ }
- public FadeOutManager() {
- mFadeConfigurations = new FadeConfigurations();
+ /**
+ * Clears the fade manager configuration that was previously set with
+ * {@link #setFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if clearing fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int clearFadeManagerConfiguration() {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.clearFadeManagerConfiguration();
+ }
}
- public FadeOutManager(FadeConfigurations fadeConfigurations) {
- mFadeConfigurations = Objects.requireNonNull(fadeConfigurations,
- "Fade configurations can not be null");
+ /**
+ * Returns the active fade manager configuration
+ *
+ * @return the {@link FadeManagerConfiguration}
+ */
+ FadeManagerConfiguration getFadeManagerConfiguration() {
+ return mFadeConfigurations.getFadeManagerConfiguration();
+ }
+
+ /**
+ * Sets the transient fade manager configuration to be used for player fade out and in
+ *
+ * @param fadeManagerConfig fade manager config that has higher priority than the existing
+ * fade manager configuration. This is expected to be transient.
+ * @return {@link AudioManager#SUCCESS} if setting fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int setTransientFadeManagerConfiguration(FadeManagerConfiguration fadeManagerConfig) {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.setTransientFadeManagerConfiguration(fadeManagerConfig);
+ }
+ }
+
+ /**
+ * Clears the transient fade manager configuration that was previously set with
+ * {@link #setTransientFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if clearing fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int clearTransientFadeManagerConfiguration() {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.clearTransientFadeManagerConfiguration();
+ }
+ }
+
+ /**
+ * Query if fade is enblead and can be enforced on players
+ *
+ * @return {@code true} if fade is enabled, {@code false} otherwise.
+ */
+ boolean isFadeEnabled() {
+ return mFadeConfigurations.isFadeEnabled();
}
// TODO explore whether a shorter fade out would be a better UX instead of not fading out at all
@@ -128,7 +188,7 @@ public final class FadeOutManager {
}
}
- void fadeOutUid(int uid, ArrayList<AudioPlaybackConfiguration> players) {
+ void fadeOutUid(int uid, List<AudioPlaybackConfiguration> players) {
Slog.i(TAG, "fadeOutUid() uid:" + uid);
synchronized (mLock) {
if (!mUidToFadedAppsMap.contains(uid)) {
@@ -148,15 +208,31 @@ 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)
*/
- void unfadeOutUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) {
+ void unfadeOutUid(int uid, Map<Integer, AudioPlaybackConfiguration> players) {
Slog.i(TAG, "unfadeOutUid() uid:" + uid);
synchronized (mLock) {
- final FadedOutApp fa = mUidToFadedAppsMap.get(uid);
+ FadedOutApp fa = mUidToFadedAppsMap.get(uid);
if (fa == null) {
return;
}
mUidToFadedAppsMap.remove(uid);
- fa.removeUnfadeAll(players);
+
+ if (!enableFadeManagerConfiguration()) {
+ fa.removeUnfadeAll(players);
+ return;
+ }
+
+ // since fade manager configs may have volume-shaper config per audio attributes,
+ // iterate through each palyer and gather respective configs for fade in
+ ArrayList<AudioPlaybackConfiguration> apcs = new ArrayList<>(players.values());
+ for (int index = 0; index < apcs.size(); index++) {
+ AudioPlaybackConfiguration apc = apcs.get(index);
+ VolumeShaper.Configuration config = mFadeConfigurations
+ .getFadeInVolumeShaperConfig(apc.getAudioAttributes());
+ fa.fadeInPlayer(apc, config);
+ }
+ // ideal case all players should be faded in
+ fa.clear();
}
}
@@ -209,16 +285,6 @@ public final class FadeOutManager {
}
}
- /**
- * Update fade configurations used for player fade operations
- * @param fadeConfigurations set of configs that define fade properties
- */
- void setFadeConfigurations(@NonNull FadeConfigurations fadeConfigurations) {
- synchronized (mLock) {
- mFadeConfigurations = fadeConfigurations;
- }
- }
-
void dump(PrintWriter pw) {
synchronized (mLock) {
for (int index = 0; index < mUidToFadedAppsMap.size(); index++) {
@@ -232,6 +298,15 @@ public final class FadeOutManager {
* Class to group players from a common app, that are faded out.
*/
private static final class FadedOutApp {
+ private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED =
+ new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY)
+ .createIfNeeded()
+ .build();
+
+ // 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();
+
private final int mUid;
// key -> piid; value -> volume shaper config applied
private final SparseArray<VolumeShaper.Configuration> mFadedPlayers = new SparseArray<>();
@@ -269,17 +344,8 @@ public final class FadeOutManager {
return;
}
if (apc.getPlayerProxy() != null) {
- try {
- PlaybackActivityMonitor.sEventLogger.enqueue(
- (new PlaybackActivityMonitor.FadeOutEvent(apc, skipRamp)).printLog(
- TAG));
- apc.getPlayerProxy().applyVolumeShaper(volShaper,
- skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
- mFadedPlayers.put(piid, volShaper);
- } catch (Exception e) {
- Slog.e(TAG, "Error fading out player piid:" + piid
- + " uid:" + apc.getClientUid(), e);
- }
+ applyVolumeShaperInternal(apc, piid, volShaper,
+ skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
} else {
if (DEBUG) {
Slog.v(TAG, "Error fading out player piid:" + piid
@@ -288,21 +354,13 @@ public final class FadeOutManager {
}
}
- void removeUnfadeAll(HashMap<Integer, AudioPlaybackConfiguration> players) {
+ void removeUnfadeAll(Map<Integer, AudioPlaybackConfiguration> players) {
for (int index = 0; index < mFadedPlayers.size(); index++) {
int piid = mFadedPlayers.keyAt(index);
final AudioPlaybackConfiguration apc = players.get(piid);
if ((apc != null) && (apc.getPlayerProxy() != null)) {
- final VolumeShaper.Configuration volShaper = mFadedPlayers.valueAt(index);
- try {
- PlaybackActivityMonitor.sEventLogger.enqueue(
- (new EventLogger.StringEvent("unfading out piid:"
- + piid)).printLog(TAG));
- apc.getPlayerProxy().applyVolumeShaper(volShaper,
- VolumeShaper.Operation.REVERSE);
- } catch (Exception e) {
- Slog.e(TAG, "Error unfading out player piid:" + piid + " uid:" + mUid, e);
- }
+ applyVolumeShaperInternal(apc, piid, /* volShaperConfig= */ null,
+ VolumeShaper.Operation.REVERSE);
} else {
// this piid was in the list of faded players, but wasn't found
if (DEBUG) {
@@ -314,8 +372,61 @@ public final class FadeOutManager {
mFadedPlayers.clear();
}
+ void fadeInPlayer(@NonNull AudioPlaybackConfiguration apc,
+ @Nullable VolumeShaper.Configuration config) {
+ int piid = Integer.valueOf(apc.getPlayerInterfaceId());
+ // if not found, no need to fade in since it was never faded out
+ if (!mFadedPlayers.contains(piid)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Player (piid: " + piid + ") for uid (" + mUid
+ + ") is not faded out, no need to fade in");
+ }
+ return;
+ }
+
+ mFadedPlayers.remove(piid);
+ if (apc.getPlayerProxy() != null) {
+ applyVolumeShaperInternal(apc, piid, config,
+ config != null ? PLAY_CREATE_IF_NEEDED : VolumeShaper.Operation.REVERSE);
+ } else {
+ if (DEBUG) {
+ Slog.v(TAG, "Error fading in player piid:" + piid
+ + ", player not found for uid " + mUid);
+ }
+ }
+ }
+
+ void clear() {
+ if (mFadedPlayers.size() > 0) {
+ if (DEBUG) {
+ Slog.v(TAG, "Non empty faded players list being cleared! Faded out players:"
+ + mFadedPlayers);
+ }
+ }
+ // should the players be faded in irrespective?
+ mFadedPlayers.clear();
+ }
+
void removeReleased(@NonNull AudioPlaybackConfiguration apc) {
mFadedPlayers.delete(Integer.valueOf(apc.getPlayerInterfaceId()));
}
+
+ private void applyVolumeShaperInternal(AudioPlaybackConfiguration apc, int piid,
+ VolumeShaper.Configuration volShaperConfig, VolumeShaper.Operation operation) {
+ VolumeShaper.Configuration config = volShaperConfig;
+ // when operation is reverse, use the fade out volume shaper config instead
+ if (operation.equals(VolumeShaper.Operation.REVERSE)) {
+ config = mFadedPlayers.get(piid);
+ }
+ try {
+ PlaybackActivityMonitor.sEventLogger.enqueue(
+ (new PlaybackActivityMonitor.FadeEvent(apc, config, operation))
+ .printLog(TAG));
+ apc.getPlayerProxy().applyVolumeShaper(config, operation);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error fading player piid:" + piid + " uid:" + mUid
+ + " operation:" + operation, e);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index 00c04ff12c89..f462539d5bbf 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -33,6 +33,7 @@ import com.android.server.audio.MediaFocusControl.AudioFocusDeathHandler;
import com.android.server.pm.UserManagerInternal;
import java.io.PrintWriter;
+import java.util.List;
/**
* @hide
@@ -534,6 +535,33 @@ public class FocusRequester {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
+ @GuardedBy("MediaFocusControl.mAudioFocusLock")
+ int dispatchFocusChangeWithFadeLocked(int focusChange, List<FocusRequester> otherActiveFrs) {
+ if (focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
+ || focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
+ || focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
+ || focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+ mFocusLossFadeLimbo = false;
+ mFocusController.restoreVShapedPlayers(this);
+ } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS
+ && mFocusController.shouldEnforceFade()) {
+ for (int index = 0; index < otherActiveFrs.size(); index++) {
+ // candidate for fade-out before a receiving a loss
+ if (mFocusController.fadeOutPlayers(otherActiveFrs.get(index), /* loser= */ this)) {
+ // 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,
+ mFocusController.getFadeOutDurationOnFocusLossMillis(
+ this.getAudioAttributes()));
+ return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
+ }
+ }
+ }
+ return dispatchFocusChange(focusChange);
+ }
+
void dispatchFocusResultFromExtPolicy(int requestResult) {
final IAudioFocusDispatcher fd = mFocusDispatcher;
if (fd == null) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 58f5d5e21cf0..0df0006c7be3 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -16,6 +16,8 @@
package com.android.server.audio;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -195,6 +197,15 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
}
return mFocusEnforcer.getFadeInDelayForOffendersMillis(aa);
}
+
+ @Override
+ public boolean shouldEnforceFade() {
+ if (!enableFadeManagerConfiguration()) {
+ return ENFORCE_FADEOUT_FOR_FOCUS_LOSS;
+ }
+
+ return mFocusEnforcer.shouldEnforceFade();
+ }
//==========================================================================================
// AudioFocus
//==========================================================================================
@@ -861,14 +872,17 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
return;
}
}
- final FocusRequester fr;
- if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
- fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
- } else {
- fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
- }
- if (fr != null) {
- fr.dispatchFocusResultFromExtPolicy(requestResult);
+ synchronized (mAudioFocusLock) {
+ FocusRequester fr = getFocusRequesterLocked(afi.getClientId(),
+ /* shouldRemove= */ requestResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED);
+ if (fr != null) {
+ fr.dispatchFocusResultFromExtPolicy(requestResult);
+ // if fade is enabled for external focus policies, apply it when setting
+ // focus result as well
+ if (enableFadeManagerConfiguration()) {
+ fr.handleFocusGainFromRequest(requestResult);
+ }
+ }
}
}
@@ -902,22 +916,78 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
+ afi.getClientId());
}
synchronized (mAudioFocusLock) {
- if (mFocusPolicy == null) {
- if (DEBUG) { Log.v(TAG, "> failed: no focus policy" ); }
+ FocusRequester fr = getFocusRequesterLocked(afi.getClientId(),
+ /* shouldRemove= */ focusChange == AudioManager.AUDIOFOCUS_LOSS);
+ if (fr == null) {
+ if (DEBUG) {
+ Log.v(TAG, "> failed: no such focus requester known");
+ }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
- final FocusRequester fr;
- if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
- fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
- } else {
- fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
- }
+ return fr.dispatchFocusChange(focusChange);
+ }
+ }
+
+ int dispatchFocusChangeWithFade(AudioFocusInfo afi, int focusChange,
+ List<AudioFocusInfo> otherActiveAfis) {
+ if (DEBUG) {
+ Log.v(TAG, "dispatchFocusChangeWithFade " + AudioManager.audioFocusToString(focusChange)
+ + " to afi client=" + afi.getClientId()
+ + " other active afis=" + otherActiveAfis);
+ }
+
+ synchronized (mAudioFocusLock) {
+ String clientId = afi.getClientId();
+ // do not remove the entry since it can be posted for fade
+ FocusRequester fr = getFocusRequesterLocked(clientId, /* shouldRemove= */ false);
if (fr == null) {
- if (DEBUG) { Log.v(TAG, "> failed: no such focus requester known" ); }
+ if (DEBUG) {
+ Log.v(TAG, "> failed: no such focus requester known");
+ }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
- return fr.dispatchFocusChange(focusChange);
+
+ // convert other AudioFocusInfo to corresponding FocusRequester
+ ArrayList<FocusRequester> otherActiveFrs = new ArrayList<>();
+ for (int index = 0; index < otherActiveAfis.size(); index++) {
+ FocusRequester otherFr = getFocusRequesterLocked(
+ otherActiveAfis.get(index).getClientId(), /* shouldRemove= */ false);
+ if (otherFr == null) {
+ continue;
+ }
+ otherActiveFrs.add(otherFr);
+ }
+
+ int status = fr.dispatchFocusChangeWithFadeLocked(focusChange, otherActiveFrs);
+ if (status != AudioManager.AUDIOFOCUS_REQUEST_DELAYED
+ && focusChange == AudioManager.AUDIOFOCUS_LOSS) {
+ mFocusOwnersForFocusPolicy.remove(clientId);
+ }
+
+ return status;
+ }
+ }
+
+ @GuardedBy("mAudioFocusLock")
+ private FocusRequester getFocusRequesterLocked(String clientId, boolean shouldRemove) {
+ if (mFocusPolicy == null) {
+ if (DEBUG) {
+ Log.v(TAG, "> failed: no focus policy");
+ }
+ return null;
+ }
+
+ FocusRequester fr;
+ if (shouldRemove) {
+ fr = mFocusOwnersForFocusPolicy.remove(clientId);
+ } else {
+ fr = mFocusOwnersForFocusPolicy.get(clientId);
+ }
+
+ if (fr == null && DEBUG) {
+ Log.v(TAG, "> failed: no such focus requester known");
}
+ return fr;
}
private void dumpExtFocusPolicyFocusOwners(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index bc9b9b4b1c88..e69fbbd2a083 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -38,6 +38,7 @@ import android.media.AudioPlaybackConfiguration;
import android.media.AudioPlaybackConfiguration.FormatInfo;
import android.media.AudioPlaybackConfiguration.PlayerMuteEvent;
import android.media.AudioSystem;
+import android.media.FadeManagerConfiguration;
import android.media.IPlaybackConfigDispatcher;
import android.media.PlayerBase;
import android.media.VolumeShaper;
@@ -156,8 +157,7 @@ public final class PlaybackActivityMonitor
private final int mMaxAlarmVolume;
private int mPrivilegedAlarmActiveCount = 0;
private final Consumer<AudioDeviceAttributes> mMuteAwaitConnectionTimeoutCb;
- private final FadeOutManager mFadeOutManager;
-
+ private final FadeOutManager mFadeOutManager = new FadeOutManager();
PlaybackActivityMonitor(Context context, int maxAlarmVolume,
Consumer<AudioDeviceAttributes> muteTimeoutCallback) {
@@ -167,7 +167,6 @@ public final class PlaybackActivityMonitor
AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
mMuteAwaitConnectionTimeoutCb = muteTimeoutCallback;
initEventHandler();
- mFadeOutManager = new FadeOutManager(new FadeConfigurations());
}
//=================================================================
@@ -971,6 +970,12 @@ public final class PlaybackActivityMonitor
return mFadeOutManager.getFadeInDelayForOffendersMillis(aa);
}
+ @Override
+ public boolean shouldEnforceFade() {
+ return mFadeOutManager.isFadeEnabled();
+ }
+
+
//=================================================================
// Track playback activity listeners
@@ -1010,6 +1015,27 @@ public final class PlaybackActivityMonitor
}
}
+ int setFadeManagerConfiguration(int focusType, FadeManagerConfiguration fadeMgrConfig) {
+ return mFadeOutManager.setFadeManagerConfiguration(fadeMgrConfig);
+ }
+
+ int clearFadeManagerConfiguration(int focusType) {
+ return mFadeOutManager.clearFadeManagerConfiguration();
+ }
+
+ FadeManagerConfiguration getFadeManagerConfiguration(int focusType) {
+ return mFadeOutManager.getFadeManagerConfiguration();
+ }
+
+ int setTransientFadeManagerConfiguration(int focusType,
+ FadeManagerConfiguration fadeMgrConfig) {
+ return mFadeOutManager.setTransientFadeManagerConfiguration(fadeMgrConfig);
+ }
+
+ int clearTransientFadeManagerConfiguration(int focusType) {
+ return mFadeOutManager.clearTransientFadeManagerConfiguration();
+ }
+
/**
* Inner class to track clients that want to be notified of playback updates
*/
@@ -1337,6 +1363,38 @@ public final class PlaybackActivityMonitor
}
}
+ static final class FadeEvent extends EventLogger.Event {
+ private final int mPlayerIId;
+ private final int mPlayerType;
+ private final int mClientUid;
+ private final int mClientPid;
+ private final AudioAttributes mPlayerAttr;
+ private final VolumeShaper.Configuration mVShaper;
+ private final VolumeShaper.Operation mVOperation;
+
+ FadeEvent(AudioPlaybackConfiguration apc, VolumeShaper.Configuration vShaper,
+ VolumeShaper.Operation vOperation) {
+ mPlayerIId = apc.getPlayerInterfaceId();
+ mClientUid = apc.getClientUid();
+ mClientPid = apc.getClientPid();
+ mPlayerAttr = apc.getAudioAttributes();
+ mPlayerType = apc.getPlayerType();
+ mVShaper = vShaper;
+ mVOperation = vOperation;
+ }
+
+ @Override
+ public String eventToString() {
+ return "Fade Event:" + " player piid:" + mPlayerIId
+ + " uid/pid:" + mClientUid + "/" + mClientPid
+ + " player type:"
+ + AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType)
+ + " attr:" + mPlayerAttr
+ + " volume shaper:" + mVShaper
+ + " volume operation:" + mVOperation;
+ }
+ }
+
private abstract static class VolumeShaperEvent extends EventLogger.Event {
private final int mPlayerIId;
private final boolean mSkipRamp;
diff --git a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
index f1d42f3571a9..4a29eca5eef7 100644
--- a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
+++ b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
@@ -79,4 +79,11 @@ public interface PlayerFocusEnforcer {
* @return delay in milliseconds
*/
long getFadeInDelayForOffendersMillis(@NonNull AudioAttributes aa);
+
+ /**
+ * Check if the fade should be enforced
+ *
+ * @return {@code true} if fade should be enforced, {@code false} otherwise
+ */
+ boolean shouldEnforceFade();
} \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java b/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java
index 6fca56134393..69817ad5035a 100644
--- a/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java
@@ -16,17 +16,21 @@
package com.android.server.audio;
-import static android.media.AudioAttributes.USAGE_MEDIA;
-import static android.media.AudioAttributes.USAGE_GAME;
-import static android.media.AudioAttributes.USAGE_ASSISTANT;
import static android.media.AudioAttributes.CONTENT_TYPE_SPEECH;
+import static android.media.AudioAttributes.USAGE_ASSISTANT;
+import static android.media.AudioAttributes.USAGE_EMERGENCY;
+import static android.media.AudioAttributes.USAGE_GAME;
+import static android.media.AudioAttributes.USAGE_MEDIA;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.media.AudioAttributes;
+import android.media.FadeManagerConfiguration;
import android.media.VolumeShaper;
+import android.platform.test.flag.junit.SetFlagsRule;
import com.google.common.truth.Expect;
@@ -42,6 +46,7 @@ import java.util.List;
public final class FadeConfigurationsTest {
private FadeConfigurations mFadeConfigurations;
private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
+ private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
private static final long DEFAULT_DELAY_FADE_IN_OFFENDERS_MS = 2000;
private static final long DURATION_FOR_UNFADEABLE_MS = 0;
private static final int TEST_UID_SYSTEM = 1000;
@@ -60,11 +65,19 @@ public final class FadeConfigurationsTest {
private static final VolumeShaper.Configuration DEFAULT_FADEOUT_VSHAPE =
new VolumeShaper.Configuration.Builder()
.setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID)
- .setCurve(/* times= */new float[]{0.f, 0.25f, 1.0f} ,
- /* volumes= */new float[]{1.f, 0.65f, 0.0f})
+ .setCurve(/* times= */ new float[]{0.f, 0.25f, 1.0f},
+ /* volumes= */ new float[]{1.f, 0.65f, 0.0f})
.setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
.setDuration(DEFAULT_FADE_OUT_DURATION_MS)
.build();
+ private static final VolumeShaper.Configuration DEFAULT_FADEIN_VSHAPE =
+ new VolumeShaper.Configuration.Builder()
+ .setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID)
+ .setCurve(/* times= */ new float[]{0.f, 0.50f, 1.0f},
+ /* volumes= */ new float[]{0.f, 0.30f, 1.0f})
+ .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+ .setDuration(DEFAULT_FADE_IN_DURATION_MS)
+ .build();
private static final AudioAttributes TEST_MEDIA_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build();
@@ -72,12 +85,18 @@ public final class FadeConfigurationsTest {
new AudioAttributes.Builder().setUsage(USAGE_GAME).build();
private static final AudioAttributes TEST_ASSISTANT_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setUsage(USAGE_ASSISTANT).build();
+ private static final AudioAttributes TEST_EMERGENCY_AUDIO_ATTRIBUTE =
+ new AudioAttributes.Builder().setSystemUsage(USAGE_EMERGENCY).build();
+
private static final AudioAttributes TEST_SPEECH_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setContentType(CONTENT_TYPE_SPEECH).build();
@Rule
public final Expect expect = Expect.create();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() {
mFadeConfigurations = new FadeConfigurations();
@@ -156,4 +175,110 @@ public final class FadeConfigurationsTest {
.that(mFadeConfigurations.isFadeable(TEST_GAME_AUDIO_ATTRIBUTE, TEST_UID_USER,
PLAYER_TYPE_AAUDIO)).isFalse();
}
+
+ @Test
+ public void testGetFadeableUsages_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<Integer> usageList = List.of(AudioAttributes.USAGE_ALARM,
+ AudioAttributes.USAGE_EMERGENCY);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ usageList,
+ /* unfadeableContentTypes= */ null, /* unfadeableUids= */ null,
+ /* unfadeableAudioAttrs= */ null);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Fadeable usages with fade manager configuration")
+ .that(fadeConfigs.getFadeableUsages()).isEqualTo(fmc.getFadeableUsages());
+ }
+
+ @Test
+ public void testGetUnfadeableContentTypes_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<Integer> contentTypesList = List.of(AudioAttributes.CONTENT_TYPE_MUSIC,
+ AudioAttributes.CONTENT_TYPE_MOVIE);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null,
+ /* unfadeableContentTypes= */ contentTypesList, /* unfadeableUids= */ null,
+ /* unfadeableAudioAttrs= */ null);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Unfadeable content types with fade manager configuration")
+ .that(fadeConfigs.getUnfadeableContentTypes())
+ .isEqualTo(fmc.getUnfadeableContentTypes());
+ }
+
+ @Test
+ public void testGetUnfadeableAudioAttributes_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<AudioAttributes> attrsList = List.of(TEST_ASSISTANT_AUDIO_ATTRIBUTE,
+ TEST_EMERGENCY_AUDIO_ATTRIBUTE);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null,
+ /* unfadeableContentTypes= */ null, /* unfadeableUids= */ null,
+ /* unfadeableAudioAttrs= */ attrsList);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Unfadeable audio attributes with fade manager configuration")
+ .that(fadeConfigs.getUnfadeableAudioAttributes())
+ .isEqualTo(fmc.getUnfadeableAudioAttributes());
+ }
+
+ @Test
+ public void testGetUnfadeableUids_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<Integer> uidsList = List.of(TEST_UID_SYSTEM, TEST_UID_USER);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null,
+ /* unfadeableContentTypes= */ null, /* unfadeableUids= */ uidsList,
+ /* unfadeableAudioAttrs= */ null);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Unfadeable uids with fade manager configuration")
+ .that(fadeConfigs.getUnfadeableUids()).isEqualTo(fmc.getUnfadeableUids());
+ }
+
+ private static FadeManagerConfiguration createFadeMgrConfig(List<Integer> fadeableUsages,
+ List<Integer> unfadeableContentTypes, List<Integer> unfadeableUids,
+ List<AudioAttributes> unfadeableAudioAttrs) {
+ FadeManagerConfiguration.Builder builder = new FadeManagerConfiguration.Builder();
+ if (fadeableUsages != null) {
+ builder.setFadeableUsages(fadeableUsages);
+ }
+ if (unfadeableContentTypes != null) {
+ builder.setUnfadeableContentTypes(unfadeableContentTypes);
+ }
+ if (unfadeableUids != null) {
+ builder.setUnfadeableUids(unfadeableUids);
+ }
+ if (unfadeableAudioAttrs != null) {
+ builder.setUnfadeableAudioAttributes(unfadeableAudioAttrs);
+ }
+ if (fadeableUsages != null) {
+ for (int index = 0; index < fadeableUsages.size(); index++) {
+ builder.setFadeOutVolumeShaperConfigForAudioAttributes(
+ createGenericAudioAttributesForUsage(fadeableUsages.get(index)),
+ DEFAULT_FADEOUT_VSHAPE);
+ }
+ }
+ if (fadeableUsages != null) {
+ for (int index = 0; index < fadeableUsages.size(); index++) {
+ builder.setFadeInVolumeShaperConfigForAudioAttributes(
+ createGenericAudioAttributesForUsage(fadeableUsages.get(index)),
+ DEFAULT_FADEIN_VSHAPE);
+ }
+ }
+
+ return builder.build();
+ }
+
+ private static AudioAttributes createGenericAudioAttributesForUsage(int usage) {
+ if (AudioAttributes.isSystemUsage(usage)) {
+ return new AudioAttributes.Builder().setSystemUsage(usage).build();
+ }
+ return new AudioAttributes.Builder().setUsage(usage).build();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
index 65059d5ca8fd..fa94821d4ff2 100644
--- a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
@@ -23,8 +23,6 @@ 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 static org.junit.Assert.assertThrows;
-
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
@@ -75,20 +73,11 @@ public final class FadeOutManagerTest {
@Before
public void setUp() {
- mFadeOutManager = new FadeOutManager(new FadeConfigurations());
+ mFadeOutManager = new FadeOutManager();
mContext = ApplicationProvider.getApplicationContext();
}
@Test
- public void constructor_nullFadeConfigurations_fails() {
- Throwable thrown = assertThrows(NullPointerException.class, () -> new FadeOutManager(
- /* FadeConfigurations= */ null));
-
- expect.withMessage("Constructor exception")
- .that(thrown).hasMessageThat().contains("Fade configurations can not be null");
- }
-
- @Test
public void testCanCauseFadeOut_forFaders_returnsTrue() {
FocusRequester winner = createFocusRequester(TEST_MEDIA_AUDIO_ATTRIBUTE, "winning-client",
"unit-test", TEST_UID_USER,