diff --git a/libraries/common/src/main/java/androidx/media3/common/audio/AudioManagerCompat.java b/libraries/common/src/main/java/androidx/media3/common/audio/AudioManagerCompat.java
index 9eb6f5b590..5a85b8b956 100644
--- a/libraries/common/src/main/java/androidx/media3/common/audio/AudioManagerCompat.java
+++ b/libraries/common/src/main/java/androidx/media3/common/audio/AudioManagerCompat.java
@@ -15,12 +15,18 @@
*/
package androidx.media3.common.audio;
+import static androidx.media3.common.util.Assertions.checkNotNull;
import static java.lang.annotation.ElementType.TYPE_USE;
+import android.content.Context;
import android.media.AudioManager;
+import android.os.Looper;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
+import androidx.annotation.Nullable;
import androidx.media3.common.C;
+import androidx.media3.common.util.BackgroundExecutor;
+import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
@@ -28,11 +34,14 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Compatibility layer for {@link AudioManager} with fallbacks for older Android versions. */
@UnstableApi
public final class AudioManagerCompat {
+ private static final String TAG = "AudioManagerCompat";
+
/**
* Audio focus gain types. One of {@link #AUDIOFOCUS_NONE}, {@link #AUDIOFOCUS_GAIN}, {@link
* #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or {@link
@@ -83,6 +92,55 @@ public final class AudioManagerCompat {
public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE =
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
+ @SuppressWarnings("NonFinalStaticField") // Lazily initialized under class lock
+ @Nullable
+ private static AudioManager audioManager;
+
+ @SuppressWarnings("NonFinalStaticField") // Lazily initialized under class lock
+ private static @MonotonicNonNull Context applicationContext;
+
+ /**
+ * Returns the {@link AudioManager}.
+ *
+ *
This method avoids potential threading issues where AudioManager keeps access to the thread
+ * it was created on until after this thread is stopped.
+ *
+ *
It is recommended to use this method from a background thread.
+ *
+ * @param context A {@link Context}.
+ * @return The {@link AudioManager}.
+ */
+ public static synchronized AudioManager getAudioManager(Context context) {
+ Context applicationContext = context.getApplicationContext();
+ if (AudioManagerCompat.applicationContext != applicationContext) {
+ // Reset cached instance if the application context changed. This should only happen in tests.
+ audioManager = null;
+ }
+ if (audioManager != null) {
+ return audioManager;
+ }
+ @Nullable Looper myLooper = Looper.myLooper();
+ if (myLooper == null || myLooper == Looper.getMainLooper()) {
+ // The AudioManager will assume the main looper as default callback anyway, so create the
+ // instance here without using BackgroundExecutor.
+ audioManager = (AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE);
+ return checkNotNull(audioManager);
+ }
+ // Create the audio manager on the BackgroundExecutor to avoid running the potentially blocking
+ // command on the main thread but still use a thread that is guaranteed to exist for the
+ // lifetime of the app.
+ ConditionVariable audioManagerSetCondition = new ConditionVariable();
+ BackgroundExecutor.get()
+ .execute(
+ () -> {
+ audioManager =
+ (AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE);
+ audioManagerSetCondition.open();
+ });
+ audioManagerSetCondition.blockUninterruptible();
+ return checkNotNull(audioManager);
+ }
+
/**
* Requests audio focus. See the {@link AudioFocusRequestCompat} for information about the options
* available to configure your request, and notification of focus gain and loss.
diff --git a/libraries/common/src/main/java/androidx/media3/common/util/BackgroundExecutor.java b/libraries/common/src/main/java/androidx/media3/common/util/BackgroundExecutor.java
index 194805f8d0..0a7453ba9e 100644
--- a/libraries/common/src/main/java/androidx/media3/common/util/BackgroundExecutor.java
+++ b/libraries/common/src/main/java/androidx/media3/common/util/BackgroundExecutor.java
@@ -31,6 +31,8 @@ public final class BackgroundExecutor {
*
*
Must only be used for quick, high-priority tasks to ensure other background tasks are not
* blocked.
+ *
+ *
The thread is guaranteed to be alive for the lifetime of the application.
*/
public static synchronized Executor get() {
if (staticInstance == null) {
@@ -42,6 +44,9 @@ public final class BackgroundExecutor {
/**
* Sets the {@link Executor} to be returned from {@link #get()}.
*
+ *
Note that the thread of the provided {@link Executor} must stay alive for the lifetime of
+ * the application.
+ *
* @param executor An {@link Executor} that runs tasks on background threads and should only be
* used for quick, high-priority tasks to ensure other background tasks are not blocked.
*/
diff --git a/libraries/common/src/main/java/androidx/media3/common/util/Util.java b/libraries/common/src/main/java/androidx/media3/common/util/Util.java
index ca58357a83..ea28a770ae 100644
--- a/libraries/common/src/main/java/androidx/media3/common/util/Util.java
+++ b/libraries/common/src/main/java/androidx/media3/common/util/Util.java
@@ -95,6 +95,7 @@ import androidx.media3.common.ParserException;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player;
import androidx.media3.common.Player.Commands;
+import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.audio.AudioProcessor;
import com.google.common.base.Ascii;
import com.google.common.io.ByteStreams;
@@ -2485,9 +2486,7 @@ public final class Util {
*/
@UnstableApi
public static int generateAudioSessionIdV21(Context context) {
- @Nullable
- AudioManager audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
- return audioManager == null ? AudioManager.ERROR : audioManager.generateAudioSessionId();
+ return AudioManagerCompat.getAudioManager(context).generateAudioSessionId();
}
/**
diff --git a/libraries/decoder_iamf/src/main/java/androidx/media3/decoder/iamf/LibiamfAudioRenderer.java b/libraries/decoder_iamf/src/main/java/androidx/media3/decoder/iamf/LibiamfAudioRenderer.java
index 9365f9b4d1..78fa93972d 100644
--- a/libraries/decoder_iamf/src/main/java/androidx/media3/decoder/iamf/LibiamfAudioRenderer.java
+++ b/libraries/decoder_iamf/src/main/java/androidx/media3/decoder/iamf/LibiamfAudioRenderer.java
@@ -26,6 +26,7 @@ import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
+import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.TraceUtil;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
@@ -95,10 +96,7 @@ public class LibiamfAudioRenderer extends DecoderAudioRenderer {
return false;
}
- AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- if (audioManager == null) {
- return false;
- }
+ AudioManager audioManager = AudioManagerCompat.getAudioManager(context);
AudioFormat audioFormat =
new AudioFormat.Builder()
.setEncoding(IamfDecoder.OUTPUT_PCM_ENCODING)
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/AudioFocusManager.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/AudioFocusManager.java
index 95902c1511..f865c72e1d 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/AudioFocusManager.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/AudioFocusManager.java
@@ -142,12 +142,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @param playerControl A {@link PlayerControl} to handle commands from this instance.
*/
public AudioFocusManager(Context context, Looper eventLooper, PlayerControl playerControl) {
- this.audioManager =
- Suppliers.memoize(
- () ->
- checkNotNull(
- (AudioManager)
- context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE)));
+ this.audioManager = Suppliers.memoize(() -> AudioManagerCompat.getAudioManager(context));
this.playerControl = playerControl;
this.eventHandler = new Handler(eventLooper);
this.audioFocusState = AUDIO_FOCUS_STATE_NOT_REQUESTED;
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java
index bf8b595841..e9e6801ed7 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java
@@ -40,6 +40,7 @@ import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
+import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
@@ -142,8 +143,7 @@ public final class AudioCapabilities {
@Nullable Intent intent,
AudioAttributes audioAttributes,
@Nullable AudioDeviceInfoApi23 routedDevice) {
- AudioManager audioManager =
- (AudioManager) checkNotNull(context.getSystemService(Context.AUDIO_SERVICE));
+ AudioManager audioManager = AudioManagerCompat.getAudioManager(context);
AudioDeviceInfoApi23 currentDevice =
routedDevice != null
? routedDevice
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilitiesReceiver.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilitiesReceiver.java
index 2e0aa8b4b4..e7cf60171b 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilitiesReceiver.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilitiesReceiver.java
@@ -31,6 +31,7 @@ import android.os.Handler;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.AudioAttributes;
+import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import java.util.Objects;
@@ -261,14 +262,12 @@ public final class AudioCapabilitiesReceiver {
public static void registerAudioDeviceCallback(
Context context, AudioDeviceCallback callback, Handler handler) {
- AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- checkNotNull(audioManager).registerAudioDeviceCallback(callback, handler);
+ AudioManagerCompat.getAudioManager(context).registerAudioDeviceCallback(callback, handler);
}
public static void unregisterAudioDeviceCallback(
Context context, AudioDeviceCallback callback) {
- AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- checkNotNull(audioManager).unregisterAudioDeviceCallback(callback);
+ AudioManagerCompat.getAudioManager(context).unregisterAudioDeviceCallback(callback);
}
private Api23() {}
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioOffloadSupportProvider.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioOffloadSupportProvider.java
index 2c0020f135..a5d285d917 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioOffloadSupportProvider.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioOffloadSupportProvider.java
@@ -26,6 +26,7 @@ import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
+import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@@ -116,17 +117,13 @@ public final class DefaultAudioOffloadSupportProvider
}
if (context != null) {
- AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- if (audioManager != null) {
- String offloadVariableRateSupportedKeyValue =
- audioManager.getParameters(/* keys= */ OFFLOAD_VARIABLE_RATE_SUPPORTED_KEY);
- isOffloadVariableRateSupported =
- offloadVariableRateSupportedKeyValue != null
- && offloadVariableRateSupportedKeyValue.equals(
- OFFLOAD_VARIABLE_RATE_SUPPORTED_KEY + "=1");
- } else {
- isOffloadVariableRateSupported = false;
- }
+ AudioManager audioManager = AudioManagerCompat.getAudioManager(context);
+ String offloadVariableRateSupportedKeyValue =
+ audioManager.getParameters(/* keys= */ OFFLOAD_VARIABLE_RATE_SUPPORTED_KEY);
+ isOffloadVariableRateSupported =
+ offloadVariableRateSupportedKeyValue != null
+ && offloadVariableRateSupportedKeyValue.equals(
+ OFFLOAD_VARIABLE_RATE_SUPPORTED_KEY + "=1");
} else {
isOffloadVariableRateSupported = false;
}
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java
index f709f50d40..55ae6f3751 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java
@@ -54,6 +54,7 @@ import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackSelectionOverride;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.TrackSelectionParameters.AudioOffloadPreferences;
+import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.BundleCollectionUtil;
import androidx.media3.common.util.Log;
@@ -4280,7 +4281,8 @@ public class DefaultTrackSelector extends MappingTrackSelector
@Nullable Context context, DefaultTrackSelector defaultTrackSelector) {
@Nullable
AudioManager audioManager =
- context == null ? null : (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ context == null ? null : AudioManagerCompat.getAudioManager(context);
+ ;
if (audioManager == null || Util.isTv(checkNotNull(context))) {
spatializer = null;
spatializationSupported = false;
diff --git a/libraries/session/src/main/java/androidx/media3/session/legacy/MediaSessionCompat.java b/libraries/session/src/main/java/androidx/media3/session/legacy/MediaSessionCompat.java
index 932060500f..2f91bedc3f 100644
--- a/libraries/session/src/main/java/androidx/media3/session/legacy/MediaSessionCompat.java
+++ b/libraries/session/src/main/java/androidx/media3/session/legacy/MediaSessionCompat.java
@@ -66,6 +66,7 @@ import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
+import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.legacy.MediaSessionManager.RemoteUserInfo;
@@ -2335,7 +2336,7 @@ public class MediaSessionCompat {
}
mContext = context;
mSessionInfo = sessionInfo;
- mAudioManager = (AudioManager) checkNotNull(context.getSystemService(Context.AUDIO_SERVICE));
+ mAudioManager = AudioManagerCompat.getAudioManager(context);
mMediaButtonReceiverComponentName = mbrComponent;
mMediaButtonReceiverIntent = mbrIntent;
mStub = new MediaSessionStub(/* mediaSessionImpl= */ this, context.getPackageName(), tag);