diff --git a/api/current.txt b/api/current.txt index 307eb5cf4409e..74ab1e45764fe 100644 --- a/api/current.txt +++ b/api/current.txt @@ -21006,6 +21006,7 @@ package android.media { method public android.media.AudioFocusRequest.Builder setAcceptsDelayedFocusGain(boolean); method public android.media.AudioFocusRequest.Builder setAudioAttributes(android.media.AudioAttributes); method public android.media.AudioFocusRequest.Builder setFocusGain(int); + method public android.media.AudioFocusRequest.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener); method public android.media.AudioFocusRequest.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler); method public android.media.AudioFocusRequest.Builder setWillPauseWhenDucked(boolean); } diff --git a/api/system-current.txt b/api/system-current.txt index d21dc2714baaf..93cc8dad531af 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -22814,6 +22814,7 @@ package android.media { method public android.media.AudioFocusRequest.Builder setAudioAttributes(android.media.AudioAttributes); method public android.media.AudioFocusRequest.Builder setFocusGain(int); method public android.media.AudioFocusRequest.Builder setLocksFocus(boolean); + method public android.media.AudioFocusRequest.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener); method public android.media.AudioFocusRequest.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler); method public android.media.AudioFocusRequest.Builder setWillPauseWhenDucked(boolean); } diff --git a/api/test-current.txt b/api/test-current.txt index 8896e16ec67e5..b16b4f4e4355e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -21114,6 +21114,7 @@ package android.media { method public android.media.AudioFocusRequest.Builder setAcceptsDelayedFocusGain(boolean); method public android.media.AudioFocusRequest.Builder setAudioAttributes(android.media.AudioAttributes); method public android.media.AudioFocusRequest.Builder setFocusGain(int); + method public android.media.AudioFocusRequest.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener); method public android.media.AudioFocusRequest.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler); method public android.media.AudioFocusRequest.Builder setWillPauseWhenDucked(boolean); } diff --git a/media/java/android/media/AudioFocusRequest.java b/media/java/android/media/AudioFocusRequest.java index 2e4d199e74ce1..29d19860eba3a 100644 --- a/media/java/android/media/AudioFocusRequest.java +++ b/media/java/android/media/AudioFocusRequest.java @@ -29,14 +29,104 @@ import android.os.Looper; * request and abandon audio focus, respectively * with {@link AudioManager#requestAudioFocus(AudioFocusRequest)} and * {@link AudioManager#abandonAudioFocusRequest(AudioFocusRequest)}. - *
In the context of describing audio focus, the term "ducking" is used. It describes a temporary - * lowering of the audio level of an application in response to another application playing audio - * concurrently. An example is during the playback of driving directions, - * a user listening to music expects the music to "duck" during the playback of the message - * announcing directions. + * + *
Audio focus is a concept introduced in API 8. It is used to convey the fact that a user can + * only focus on a single audio stream at a time, e.g. listening to music or a podcast, but not + * both at the same time. In some cases, multiple audio streams can be playing at the same time, + * but there is only one the user would really listen to (focus on), while the other plays in + * the background. An example of this is driving directions being spoken while music plays at + * a reduced volume (a.k.a. ducking). + *
When an application requests audio focus, it expresses its intention to “own” audio focus to
+ * play audio. Let’s review the different types of focus requests, the return value after a request,
+ * and the responses to a loss.
+ *
Note: applications should not play anything until granted focus.
+ *
+ * The different types of focus requests
+ *
There are four focus request types. A successful focus request with each will yield different + * behaviors by the system and the other application that previously held audio focus. + *
An {@code AudioFocusRequest} instance always contains one of the four types of requests + * explained above. It is passed when building an {@code AudioFocusRequest} instance with its + * builder in the {@link Builder} constructor {@link Builder#Builder(int)}, or with + * {@link Builder#setFocusGain(int)} after copying an existing instance with + * {@link Builder#Builder(AudioFocusRequest)}. + * + *
Any focus request is qualified by the {@link AudioAttributes}
+ * (see {@link Builder#setAudioAttributes(AudioAttributes)}) that describe the audio use case that
+ * will follow the request (once it's successful or granted). It is recommended to use the
+ * same {@code AudioAttributes} for the request as the attributes you are using for audio/media
+ * playback.
+ *
If no attributes are set, default attributes of {@link AudioAttributes#USAGE_MEDIA} are used.
+ *
+ *
Audio focus can be "locked" by the system for a number of reasons: during a phone call, when
+ * the car to which the device is connected plays an emergency message... To support these
+ * situations, the application can request to be notified when its request is fulfilled, by flagging
+ * its request as accepting delayed focus, with {@link Builder#setAcceptsDelayedFocusGain(boolean)}.
+ *
If focus is requested while being locked by the system,
+ * {@link AudioManager#requestAudioFocus(AudioFocusRequest)} will return
+ * {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}. When focus isn't locked anymore, the focus
+ * listener set with {@link Builder#setOnAudioFocusChangeListener(OnAudioFocusChangeListener)}
+ * or with {@link Builder#setOnAudioFocusChangeListener(OnAudioFocusChangeListener, Handler)} will
+ * be called to notify the application it now owns audio focus.
+ *
+ *
When an application requested audio focus with
+ * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, the system will duck the current focus
+ * owner. Note that this behavior is new for Android O, whereas applications targeting SDK
+ * up to API 25, applications had to implement the ducking themselves when they received a focus
+ * loss of {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
+ *
But ducking is not always the behavior expected by the user. A typical example is when the
+ * device plays driving directions while the user is listening to an audio book or podcast, and
+ * expects the audio playback to pause, instead of duck, as it is hard to understand a navigation
+ * prompt and spoken content at the same time. Therefore the system will not automatically duck
+ * when it detects it would be ducking spoken content: such content is detected when the
+ * {@code AudioAttributes} of the player are qualified by
+ * {@link AudioAttributes#CONTENT_TYPE_SPEECH}. Refer for instance to
+ * {@link AudioAttributes.Builder#setContentType(int)} and
+ * {@link MediaPlayer#setAudioAttributes(AudioAttributes)} if you are writing a media playback
+ * application for audio book, podcasts... Since the system will not automatically duck applications
+ * that play speech, it calls their focus listener instead to notify them of
+ * {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}, so they can pause instead. Note that
+ * this behavior is independent of the use of {@code AudioFocusRequest}, but tied to the use
+ * of {@code AudioAttributes}.
+ *
If your application requires pausing instead of ducking for any other reason than playing
+ * speech, you can also declare so with {@link Builder#setWillPauseWhenDucked(boolean)}, which will
+ * cause the system to call your focus listener instead of automatically ducking.
+ *
*/
-// TODO use this class to provide more documentation about audio focus and the new behaviors
-// describe up to N, and after.
public final class AudioFocusRequest {
// default attributes for the request when not specified
@@ -245,6 +335,27 @@ public final class AudioFocusRequest {
return this;
}
+ /**
+ * Sets the listener called when audio focus changes after being requested with
+ * {@link AudioManager#requestAudioFocus(AudioFocusRequest)}, and until being abandoned
+ * with {@link AudioManager#abandonAudioFocusRequest(AudioFocusRequest)}.
+ * Note that only focus changes (gains and losses) affecting the focus owner are reported,
+ * not gains and losses of other focus requesters in the system.
+ * Notifications are delivered on the main {@link Looper}.
+ * @param listener the listener receiving the focus change notifications.
+ * @return this {@code Builder} instance.
+ * @throws NullPointerException thrown when a null focus listener is used.
+ */
+ public @NonNull Builder setOnAudioFocusChangeListener(
+ @NonNull OnAudioFocusChangeListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("Illegal null focus listener");
+ }
+ mFocusListener = listener;
+ mListenerHandler = null;
+ return this;
+ }
+
/**
* Sets the listener called when audio focus changes after being requested with
* {@link AudioManager#requestAudioFocus(AudioFocusRequest)}, and until being abandoned
@@ -253,17 +364,14 @@ public final class AudioFocusRequest {
* not gains and losses of other focus requesters in the system.
* @param listener the listener receiving the focus change notifications.
* @param handler the {@link Handler} for the thread on which to execute
- * the notifications. If {@code null}, the {@code Handler} associated with the main
- * {@link Looper} will be used.
+ * the notifications.
* @return this {@code Builder} instance.
- * @throws IllegalArgumentException thrown when a non-null handler is used with a null
- * listener.
+ * @throws NullPointerException thrown when a null focus listener or handler is used.
*/
public @NonNull Builder setOnAudioFocusChangeListener(
- @Nullable OnAudioFocusChangeListener listener, @Nullable Handler handler) {
- if (listener == null && handler != null) {
- throw new IllegalArgumentException(
- "Illegal non-null handler without a focus listener");
+ @NonNull OnAudioFocusChangeListener listener, @NonNull Handler handler) {
+ if (listener == null || handler == null) {
+ throw new NullPointerException("Illegal null focus listener or handler");
}
mFocusListener = listener;
mListenerHandler = handler;
@@ -272,7 +380,7 @@ public final class AudioFocusRequest {
/**
* Sets the {@link AudioAttributes} to be associated with the focus request, and which
- * describe the use case describing why focus is requested.
+ * describe the use case for which focus is requested.
* As the focus requests typically precede audio playback, this information is used on
* certain platforms to declare the subsequent playback use case. It is therefore good
* practice to use in this method the same {@code AudioAttributes} as used for