diff options
| author | 2023-12-19 18:03:58 +0000 | |
|---|---|---|
| committer | 2023-12-19 18:03:58 +0000 | |
| commit | cd5f79d201fe1305be869433ef29d406a42bfb91 (patch) | |
| tree | f1ef0ef298bd68bfeb435b65d055498cf30fe63a | |
| parent | 68fb1e93a1cc90c01822b716d585f181658fba19 (diff) | |
| parent | 0428580bb44e666807b80c4ab5ce254605c4d7c5 (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
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, |