Add util to obtain AudioManager

AudioManager internally assumes the thread is was created on can be
used as a callback thread. Since the AudioManager is only created
once in the lifetime of an app, we need to make sure its obtained
on a thread that stays alive.

#cherrypick

PiperOrigin-RevId: 732072255
(cherry picked from commit 2088697a19ac85feb26ba2521a70647803246571)
This commit is contained in:
tonihei 2025-02-28 03:21:38 -08:00 committed by oceanjules
parent 595fd48cd7
commit 766dbd497e
10 changed files with 86 additions and 32 deletions

View File

@ -15,12 +15,18 @@
*/ */
package androidx.media3.common.audio; package androidx.media3.common.audio;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import android.content.Context;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Looper;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.media3.common.C; 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.Log;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
@ -28,11 +34,14 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Compatibility layer for {@link AudioManager} with fallbacks for older Android versions. */ /** Compatibility layer for {@link AudioManager} with fallbacks for older Android versions. */
@UnstableApi @UnstableApi
public final class AudioManagerCompat { public final class AudioManagerCompat {
private static final String TAG = "AudioManagerCompat";
/** /**
* Audio focus gain types. One of {@link #AUDIOFOCUS_NONE}, {@link #AUDIOFOCUS_GAIN}, {@link * Audio focus gain types. One of {@link #AUDIOFOCUS_NONE}, {@link #AUDIOFOCUS_GAIN}, {@link
* #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or {@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 = public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE =
AudioManager.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}.
*
* <p>This method avoids potential threading issues where AudioManager keeps access to the thread
* it was created on until after this thread is stopped.
*
* <p>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 * Requests audio focus. See the {@link AudioFocusRequestCompat} for information about the options
* available to configure your request, and notification of focus gain and loss. * available to configure your request, and notification of focus gain and loss.

View File

@ -31,6 +31,8 @@ public final class BackgroundExecutor {
* *
* <p>Must only be used for quick, high-priority tasks to ensure other background tasks are not * <p>Must only be used for quick, high-priority tasks to ensure other background tasks are not
* blocked. * blocked.
*
* <p>The thread is guaranteed to be alive for the lifetime of the application.
*/ */
public static synchronized Executor get() { public static synchronized Executor get() {
if (staticInstance == null) { if (staticInstance == null) {
@ -42,6 +44,9 @@ public final class BackgroundExecutor {
/** /**
* Sets the {@link Executor} to be returned from {@link #get()}. * Sets the {@link Executor} to be returned from {@link #get()}.
* *
* <p>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 * @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. * used for quick, high-priority tasks to ensure other background tasks are not blocked.
*/ */

View File

@ -95,6 +95,7 @@ import androidx.media3.common.ParserException;
import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player; import androidx.media3.common.Player;
import androidx.media3.common.Player.Commands; import androidx.media3.common.Player.Commands;
import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.audio.AudioProcessor; import androidx.media3.common.audio.AudioProcessor;
import com.google.common.base.Ascii; import com.google.common.base.Ascii;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
@ -2485,9 +2486,7 @@ public final class Util {
*/ */
@UnstableApi @UnstableApi
public static int generateAudioSessionIdV21(Context context) { public static int generateAudioSessionIdV21(Context context) {
@Nullable return AudioManagerCompat.getAudioManager(context).generateAudioSessionId();
AudioManager audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
return audioManager == null ? AudioManager.ERROR : audioManager.generateAudioSessionId();
} }
/** /**

View File

@ -26,6 +26,7 @@ import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.TraceUtil; import androidx.media3.common.util.TraceUtil;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
@ -95,10 +96,7 @@ public class LibiamfAudioRenderer extends DecoderAudioRenderer<IamfDecoder> {
return false; return false;
} }
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); AudioManager audioManager = AudioManagerCompat.getAudioManager(context);
if (audioManager == null) {
return false;
}
AudioFormat audioFormat = AudioFormat audioFormat =
new AudioFormat.Builder() new AudioFormat.Builder()
.setEncoding(IamfDecoder.OUTPUT_PCM_ENCODING) .setEncoding(IamfDecoder.OUTPUT_PCM_ENCODING)

View File

@ -142,12 +142,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @param playerControl A {@link PlayerControl} to handle commands from this instance. * @param playerControl A {@link PlayerControl} to handle commands from this instance.
*/ */
public AudioFocusManager(Context context, Looper eventLooper, PlayerControl playerControl) { public AudioFocusManager(Context context, Looper eventLooper, PlayerControl playerControl) {
this.audioManager = this.audioManager = Suppliers.memoize(() -> AudioManagerCompat.getAudioManager(context));
Suppliers.memoize(
() ->
checkNotNull(
(AudioManager)
context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE)));
this.playerControl = playerControl; this.playerControl = playerControl;
this.eventHandler = new Handler(eventLooper); this.eventHandler = new Handler(eventLooper);
this.audioFocusState = AUDIO_FOCUS_STATE_NOT_REQUESTED; this.audioFocusState = AUDIO_FOCUS_STATE_NOT_REQUESTED;

View File

@ -40,6 +40,7 @@ import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -142,8 +143,7 @@ public final class AudioCapabilities {
@Nullable Intent intent, @Nullable Intent intent,
AudioAttributes audioAttributes, AudioAttributes audioAttributes,
@Nullable AudioDeviceInfoApi23 routedDevice) { @Nullable AudioDeviceInfoApi23 routedDevice) {
AudioManager audioManager = AudioManager audioManager = AudioManagerCompat.getAudioManager(context);
(AudioManager) checkNotNull(context.getSystemService(Context.AUDIO_SERVICE));
AudioDeviceInfoApi23 currentDevice = AudioDeviceInfoApi23 currentDevice =
routedDevice != null routedDevice != null
? routedDevice ? routedDevice

View File

@ -31,6 +31,7 @@ import android.os.Handler;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.media3.common.AudioAttributes; import androidx.media3.common.AudioAttributes;
import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import java.util.Objects; import java.util.Objects;
@ -261,14 +262,12 @@ public final class AudioCapabilitiesReceiver {
public static void registerAudioDeviceCallback( public static void registerAudioDeviceCallback(
Context context, AudioDeviceCallback callback, Handler handler) { Context context, AudioDeviceCallback callback, Handler handler) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); AudioManagerCompat.getAudioManager(context).registerAudioDeviceCallback(callback, handler);
checkNotNull(audioManager).registerAudioDeviceCallback(callback, handler);
} }
public static void unregisterAudioDeviceCallback( public static void unregisterAudioDeviceCallback(
Context context, AudioDeviceCallback callback) { Context context, AudioDeviceCallback callback) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); AudioManagerCompat.getAudioManager(context).unregisterAudioDeviceCallback(callback);
checkNotNull(audioManager).unregisterAudioDeviceCallback(callback);
} }
private Api23() {} private Api23() {}

View File

@ -26,6 +26,7 @@ import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -116,8 +117,7 @@ public final class DefaultAudioOffloadSupportProvider
} }
if (context != null) { if (context != null) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); AudioManager audioManager = AudioManagerCompat.getAudioManager(context);
if (audioManager != null) {
String offloadVariableRateSupportedKeyValue = String offloadVariableRateSupportedKeyValue =
audioManager.getParameters(/* keys= */ OFFLOAD_VARIABLE_RATE_SUPPORTED_KEY); audioManager.getParameters(/* keys= */ OFFLOAD_VARIABLE_RATE_SUPPORTED_KEY);
isOffloadVariableRateSupported = isOffloadVariableRateSupported =
@ -127,9 +127,6 @@ public final class DefaultAudioOffloadSupportProvider
} else { } else {
isOffloadVariableRateSupported = false; isOffloadVariableRateSupported = false;
} }
} else {
isOffloadVariableRateSupported = false;
}
return isOffloadVariableRateSupported; return isOffloadVariableRateSupported;
} }

View File

@ -54,6 +54,7 @@ import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackSelectionOverride; import androidx.media3.common.TrackSelectionOverride;
import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.TrackSelectionParameters.AudioOffloadPreferences; import androidx.media3.common.TrackSelectionParameters.AudioOffloadPreferences;
import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.BundleCollectionUtil; import androidx.media3.common.util.BundleCollectionUtil;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
@ -4280,7 +4281,8 @@ public class DefaultTrackSelector extends MappingTrackSelector
@Nullable Context context, DefaultTrackSelector defaultTrackSelector) { @Nullable Context context, DefaultTrackSelector defaultTrackSelector) {
@Nullable @Nullable
AudioManager audioManager = AudioManager audioManager =
context == null ? null : (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); context == null ? null : AudioManagerCompat.getAudioManager(context);
;
if (audioManager == null || Util.isTv(checkNotNull(context))) { if (audioManager == null || Util.isTv(checkNotNull(context))) {
spatializer = null; spatializer = null;
spatializationSupported = false; spatializationSupported = false;

View File

@ -66,6 +66,7 @@ import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo; import androidx.annotation.RestrictTo;
import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.NullableType; import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.legacy.MediaSessionManager.RemoteUserInfo; import androidx.media3.session.legacy.MediaSessionManager.RemoteUserInfo;
@ -2335,7 +2336,7 @@ public class MediaSessionCompat {
} }
mContext = context; mContext = context;
mSessionInfo = sessionInfo; mSessionInfo = sessionInfo;
mAudioManager = (AudioManager) checkNotNull(context.getSystemService(Context.AUDIO_SERVICE)); mAudioManager = AudioManagerCompat.getAudioManager(context);
mMediaButtonReceiverComponentName = mbrComponent; mMediaButtonReceiverComponentName = mbrComponent;
mMediaButtonReceiverIntent = mbrIntent; mMediaButtonReceiverIntent = mbrIntent;
mStub = new MediaSessionStub(/* mediaSessionImpl= */ this, context.getPackageName(), tag); mStub = new MediaSessionStub(/* mediaSessionImpl= */ this, context.getPackageName(), tag);