AudioManagerCompat.java

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.media;

import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;

import androidx.annotation.DoNotInline;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat.StreamType;

/** Compatibility library for {@link AudioManager} with fallbacks for older platforms. */
public final class AudioManagerCompat {

    private static final String TAG = "AudioManCompat";

    /**
     * Used to indicate a gain of audio focus, or a request of audio focus, of unknown duration.
     *
     * @see AudioManager.OnAudioFocusChangeListener#onAudioFocusChange(int)
     * @see #requestAudioFocus(AudioManager, AudioFocusRequestCompat)
     */
    public static final int AUDIOFOCUS_GAIN = AudioManager.AUDIOFOCUS_GAIN;
    /**
     * Used to indicate a temporary gain or request of audio focus, anticipated to last a short
     * amount of time. Examples of temporary changes are the playback of driving directions, or an
     * event notification.
     *
     * @see AudioManager.OnAudioFocusChangeListener#onAudioFocusChange(int)
     * @see #requestAudioFocus(AudioManager, AudioFocusRequestCompat)
     */
    public static final int AUDIOFOCUS_GAIN_TRANSIENT = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
    /**
     * Used to indicate a temporary request of audio focus, anticipated to last a short amount of
     * time, and where it is acceptable for other audio applications to keep playing after having
     * lowered their output level (also referred to as "ducking"). Examples of temporary changes are
     * the playback of driving directions where playback of music in the background is acceptable.
     *
     * @see AudioManager.OnAudioFocusChangeListener#onAudioFocusChange(int)
     * @see #requestAudioFocus(AudioManager, AudioFocusRequestCompat)
     */
    public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK =
            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
    /**
     * Used to indicate a temporary request of audio focus, anticipated to last a short amount of
     * time, during which no other applications, or system components, should play anything.
     * Examples of exclusive and transient audio focus requests are voice memo recording and speech
     * recognition, during which the system shouldn't play any notifications, and media playback
     * should have paused.
     *
     * @see #requestAudioFocus(AudioManager, AudioFocusRequestCompat)
     */
    public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE =
            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;

    /**
     * Requests audio focus. See the {@link AudioFocusRequestCompat} for information about the
     * options available to configure your request, and notification of focus gain and loss.
     *
     * @param focusRequest an {@link AudioFocusRequestCompat} instance used to configure how focus
     *     is requested.
     * @return {@link AudioManager#AUDIOFOCUS_REQUEST_FAILED} or {@link
     *     AudioManager#AUDIOFOCUS_REQUEST_GRANTED}.
     * @throws NullPointerException if passed a null argument
     */
    @SuppressWarnings("deprecation")
    public static int requestAudioFocus(
            @NonNull AudioManager audioManager, @NonNull AudioFocusRequestCompat focusRequest) {
        if (audioManager == null) {
            throw new IllegalArgumentException("AudioManager must not be null");
        }
        if (focusRequest == null) {
            throw new IllegalArgumentException("AudioFocusRequestCompat must not be null");
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            return Api26Impl.requestAudioFocus(audioManager, focusRequest.getAudioFocusRequest());
        } else {
            return audioManager.requestAudioFocus(
                    focusRequest.getOnAudioFocusChangeListener(),
                    focusRequest.getAudioAttributesCompat().getLegacyStreamType(),
                    focusRequest.getFocusGain());
        }
    }

    /**
     * Abandon audio focus. Causes the previous focus owner, if any, to receive focus.
     *
     * @param focusRequest the {@link AudioFocusRequestCompat} that was used when requesting focus
     *     with {@link #requestAudioFocus(AudioManager, AudioFocusRequestCompat)}.
     * @return {@link AudioManager#AUDIOFOCUS_REQUEST_FAILED} or {@link
     *     AudioManager#AUDIOFOCUS_REQUEST_GRANTED}
     * @throws IllegalArgumentException if passed a null argument
     */
    @SuppressWarnings("deprecation")
    public static int abandonAudioFocusRequest(
            @NonNull AudioManager audioManager, @NonNull AudioFocusRequestCompat focusRequest) {
        if (audioManager == null) {
            throw new IllegalArgumentException("AudioManager must not be null");
        }
        if (focusRequest == null) {
            throw new IllegalArgumentException("AudioFocusRequestCompat must not be null");
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            return Api26Impl.abandonAudioFocusRequest(audioManager,
                    focusRequest.getAudioFocusRequest());
        } else {
            return audioManager.abandonAudioFocus(focusRequest.getOnAudioFocusChangeListener());
        }
    }

    /**
     * Returns the maximum volume index for a particular stream.
     *
     * @param streamType The stream type whose maximum volume index is returned.
     * @return The maximum valid volume index for the stream.
     */
    @IntRange(from = 0)
    public static int getStreamMaxVolume(@NonNull AudioManager audioManager,
            @StreamType int streamType) {
        return audioManager.getStreamMaxVolume(streamType);
    }

    /**
     * Returns the minimum volume index for a particular stream.
     *
     * @param streamType The stream type whose minimum volume index is returned.
     * @return The minimum valid volume index for the stream.
     */
    @IntRange(from = 0)
    public static int getStreamMinVolume(@NonNull AudioManager audioManager,
            @StreamType int streamType) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            return Api28Impl.getStreamMinVolume(audioManager, streamType);
        } else {
            return 0;
        }
    }

    /**
     * Indicates if the device implements a fixed volume policy.
     *
     * <p>Some devices may not have volume control and may operate at a fixed volume, and may not
     * enable muting or changing the volume of audio streams. This method will return {@code true}
     * on such devices.
     *
     * <p>Compatibility: It returns {@code false} on API level below 21 even if the device has
     * fixed volume.
     */
    public static boolean isVolumeFixed(@NonNull AudioManager audioManager) {
        if (Build.VERSION.SDK_INT >= 21) {
            return Api21Impl.isVolumeFixed(audioManager);
        } else {
            return false;
        }
    }

    private AudioManagerCompat() {}

    @RequiresApi(21)
    private static class Api21Impl {

        @DoNotInline
        static boolean isVolumeFixed(AudioManager audioManager) {
            return audioManager.isVolumeFixed();
        }

        private Api21Impl() {}
    }

    @RequiresApi(26)
    private static class Api26Impl {

        @DoNotInline
        static int abandonAudioFocusRequest(AudioManager audioManager,
                AudioFocusRequest focusRequest) {
            return audioManager.abandonAudioFocusRequest(focusRequest);
        }

        @DoNotInline
        static int requestAudioFocus(AudioManager audioManager, AudioFocusRequest focusRequest) {
            return audioManager.requestAudioFocus(focusRequest);
        }

        private Api26Impl() {}
    }

    @RequiresApi(28)
    private static class Api28Impl {

        @DoNotInline
        static int getStreamMinVolume(AudioManager audioManager, int streamType) {
            return audioManager.getStreamMinVolume(streamType);
        }

        private Api28Impl() {}
    }
}