Java tutorial
/* * Copyright (C) 2017 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 android.media; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.media.AudioManager.OnAudioFocusChangeListener; import android.os.Bundle; import android.os.Handler; import android.os.Looper; /** * A class to encapsulate information about an audio focus request. * An {@code AudioFocusRequest} instance is built by {@link Builder}, and is used to * request and abandon audio focus, respectively * with {@link AudioManager#requestAudioFocus(AudioFocusRequest)} and * {@link AudioManager#abandonAudioFocusRequest(AudioFocusRequest)}. * * <h3>What is audio focus?</h3> * <p>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). * <p>When an application requests audio focus, it expresses its intention to own? audio focus to * play audio. Lets review the different types of focus requests, the return value after a request, * and the responses to a loss. * <p class="note">Note: applications should not play anything until granted focus.</p> * * <h3>The different types of focus requests</h3> * <p>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. * <ul> * <li>{@link AudioManager#AUDIOFOCUS_GAIN} expresses the fact that your application is now the * sole source of audio that the user is listening to. The duration of the audio playback is * unknown, and is possibly very long: after the user finishes interacting with your application, * (s)he doesnt expect another audio stream to resume. Examples of uses of this focus gain are * for music playback, for a game or a video player.</li> * * <li>{@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT} is for a situation when you know your * application is temporarily grabbing focus from the current owner, but the user expects playback * to go back to where it was once your application no longer requires audio focus. An example is * for playing an alarm, or during a VoIP call. The playback is known to be finite: the alarm will * time-out or be dismissed, the VoIP call has a beginning and an end. When any of those events * ends, and if the user was listening to music when it started, the user expects music to resume, * but didnt wish to listen to both at the same time.</li> * * <li>{@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}: this focus request type is similar * to {@code AUDIOFOCUS_GAIN_TRANSIENT} for the temporary aspect of the focus request, but it also * expresses the fact during the time you own focus, you allow another application to keep playing * at a reduced volume, ducked?. Examples are when playing driving directions or notifications, * its ok for music to keep playing, but not loud enough that it would prevent the directions to * be hard to understand. A typical attenuation by the ducked? application is a factor of 0.2f * (or -14dB), that can for instance be applied with {@code MediaPlayer.setVolume(0.2f)} when * using this class for playback.</li> * * <li>{@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE} is also for a temporary request, * but also expresses that your application expects the device to not play anything else. This is * typically used if you are doing audio recording or speech recognition, and dont want for * examples notifications to be played by the system during that time.</li> * </ul> * * <p>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 AudioFocusRequest.Builder#AudioFocusRequest.Builder(int)}, or * with {@link AudioFocusRequest.Builder#setFocusGain(int)} after copying an existing instance with * {@link AudioFocusRequest.Builder#AudioFocusRequest.Builder(AudioFocusRequest)}. * * <h3>Qualifying your focus request</h3> * <h4>Use case requiring a focus request</h4> * <p>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. * <br>If no attributes are set, default attributes of {@link AudioAttributes#USAGE_MEDIA} are used. * * <h4>Delayed focus</h4> * <p>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)}. * <br>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. * * <h4>Pausing vs ducking</h4> * <p>When an application requested audio focus with * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, the system will duck the current focus * owner. * <p class="note">Note: this behavior is <b>new for Android O</b>, whereas applications targeting * SDK level up to API 25 had to implement the ducking themselves when they received a focus * loss of {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}. * <p>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}. * <p>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. * * <h4>Example</h4> * <p>The example below covers the following steps to be found in any application that would play * audio, and use audio focus. Here we play an audio book, and our application is intended to pause * rather than duck when it loses focus. These steps consist in: * <ul> * <li>Creating {@code AudioAttributes} to be used for the playback and the focus request.</li> * <li>Configuring and creating the {@code AudioFocusRequest} instance that defines the intended * focus behaviors.</li> * <li>Requesting audio focus and checking the return code to see if playback can happen right * away, or is delayed.</li> * <li>Implementing a focus change listener to respond to focus gains and losses.</li> * </ul> * <p> * <pre class="prettyprint"> * // initialization of the audio attributes and focus request * mAudioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE); * mPlaybackAttributes = new AudioAttributes.Builder() * .setUsage(AudioAttributes.USAGE_MEDIA) * .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) * .build(); * mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) * .setAudioAttributes(mPlaybackAttributes) * .setAcceptsDelayedFocusGain(true) * .setWillPauseWhenDucked(true) * .setOnAudioFocusChangeListener(this, mMyHandler) * .build(); * mMediaPlayer = new MediaPlayer(); * mMediaPlayer.setAudioAttributes(mPlaybackAttributes); * final Object mFocusLock = new Object(); * * boolean mPlaybackDelayed = false; * * // requesting audio focus * int res = mAudioManager.requestAudioFocus(mFocusRequest); * synchronized (mFocusLock) { * if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { * mPlaybackDelayed = false; * } else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { * mPlaybackDelayed = false; * playbackNow(); * } else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) { * mPlaybackDelayed = true; * } * } * * // implementation of the OnAudioFocusChangeListener * @Override * public void onAudioFocusChange(int focusChange) { * switch (focusChange) { * case AudioManager.AUDIOFOCUS_GAIN: * if (mPlaybackDelayed || mResumeOnFocusGain) { * synchronized (mFocusLock) { * mPlaybackDelayed = false; * mResumeOnFocusGain = false; * } * playbackNow(); * } * break; * case AudioManager.AUDIOFOCUS_LOSS: * synchronized (mFocusLock) { * // this is not a transient loss, we shouldn't automatically resume for now * mResumeOnFocusGain = false; * mPlaybackDelayed = false; * } * pausePlayback(); * break; * case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: * case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: * // we handle all transient losses the same way because we never duck audio books * synchronized (mFocusLock) { * // we should only resume if playback was interrupted * mResumeOnFocusGain = mMediaPlayer.isPlaying(); * mPlaybackDelayed = false; * } * pausePlayback(); * break; * } * } * * // Important: * // Also set "mResumeOnFocusGain" to false when the user pauses or stops playback: this way your * // application doesn't automatically restart when it gains focus, even though the user had * // stopped it. * </pre> */ public final class AudioFocusRequest { // default attributes for the request when not specified private final static AudioAttributes FOCUS_DEFAULT_ATTR = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA).build(); /** @hide */ public static final String KEY_ACCESSIBILITY_FORCE_FOCUS_DUCKING = "a11y_force_ducking"; private final OnAudioFocusChangeListener mFocusListener; // may be null private final Handler mListenerHandler; // may be null private final AudioAttributes mAttr; // never null private final int mFocusGain; private final int mFlags; private AudioFocusRequest(OnAudioFocusChangeListener listener, Handler handler, AudioAttributes attr, int focusGain, int flags) { mFocusListener = listener; mListenerHandler = handler; mFocusGain = focusGain; mAttr = attr; mFlags = flags; } /** * @hide * Checks whether a focus gain constant is a valid value for an audio focus request. * @param focusGain value to check * @return true if focusGain is a valid value for an audio focus request. */ final static boolean isValidFocusGain(int focusGain) { switch (focusGain) { case AudioManager.AUDIOFOCUS_GAIN: case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: return true; default: return false; } } /** * @hide * Returns the focus change listener set for this {@code AudioFocusRequest}. * @return null if no {@link AudioManager.OnAudioFocusChangeListener} was set. */ @TestApi public @Nullable OnAudioFocusChangeListener getOnAudioFocusChangeListener() { return mFocusListener; } /** * @hide * Returns the {@link Handler} to be used for the focus change listener. * @return the same {@code Handler} set in. * {@link Builder#setOnAudioFocusChangeListener(OnAudioFocusChangeListener, Handler)}, or null * if no listener was set. */ public @Nullable Handler getOnAudioFocusChangeListenerHandler() { return mListenerHandler; } /** * Returns the {@link AudioAttributes} set for this {@code AudioFocusRequest}, or the default * attributes if none were set. * @return non-null {@link AudioAttributes}. */ public @NonNull AudioAttributes getAudioAttributes() { return mAttr; } /** * Returns the type of audio focus request configured for this {@code AudioFocusRequest}. * @return one of {@link AudioManager#AUDIOFOCUS_GAIN}, * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT}, * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. */ public int getFocusGain() { return mFocusGain; } /** * Returns whether the application that would use this {@code AudioFocusRequest} would pause * when it is requested to duck. * @return the duck/pause behavior. */ public boolean willPauseWhenDucked() { return (mFlags & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) == AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS; } /** * Returns whether the application that would use this {@code AudioFocusRequest} supports * a focus gain granted after a temporary request failure. * @return whether delayed focus gain is supported. */ public boolean acceptsDelayedFocusGain() { return (mFlags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == AudioManager.AUDIOFOCUS_FLAG_DELAY_OK; } /** * @hide * Returns whether audio focus will be locked (i.e. focus cannot change) as a result of this * focus request being successful. * @return whether this request will lock focus. */ @SystemApi public boolean locksFocus() { return (mFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK; } int getFlags() { return mFlags; } /** * Builder class for {@link AudioFocusRequest} objects. * <p>See {@link AudioFocusRequest} for an example of building an instance with this builder. * <br>The default values for the instance to be built are: * <table> * <tr><td>focus listener and handler</td><td>none</td></tr> * <tr><td>{@code AudioAttributes}</td><td>attributes with usage set to * {@link AudioAttributes#USAGE_MEDIA}</td></tr> * <tr><td>pauses on duck</td><td>false</td></tr> * <tr><td>supports delayed focus grant</td><td>false</td></tr> * </table> */ public static final class Builder { private OnAudioFocusChangeListener mFocusListener; private Handler mListenerHandler; private AudioAttributes mAttr = FOCUS_DEFAULT_ATTR; private int mFocusGain; private boolean mPausesOnDuck = false; private boolean mDelayedFocus = false; private boolean mFocusLocked = false; private boolean mA11yForceDucking = false; /** * Constructs a new {@code Builder}, and specifies how audio focus * will be requested. Valid values for focus requests are * {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT}, * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. * <p>By default there is no focus change listener, delayed focus is not supported, ducking * is suitable for the application, and the <code>AudioAttributes</code> * have a usage of {@link AudioAttributes#USAGE_MEDIA}. * @param focusGain the type of audio focus gain that will be requested * @throws IllegalArgumentException thrown when an invalid focus gain type is used */ public Builder(int focusGain) { setFocusGain(focusGain); } /** * Constructs a new {@code Builder} with all the properties of the {@code AudioFocusRequest} * passed as parameter. * Use this method when you want a new request to differ only by some properties. * @param requestToCopy the non-null {@code AudioFocusRequest} to build a duplicate from. * @throws IllegalArgumentException thrown when a null {@code AudioFocusRequest} is used. */ public Builder(@NonNull AudioFocusRequest requestToCopy) { if (requestToCopy == null) { throw new IllegalArgumentException("Illegal null AudioFocusRequest"); } mAttr = requestToCopy.mAttr; mFocusListener = requestToCopy.mFocusListener; mListenerHandler = requestToCopy.mListenerHandler; mFocusGain = requestToCopy.mFocusGain; mPausesOnDuck = requestToCopy.willPauseWhenDucked(); mDelayedFocus = requestToCopy.acceptsDelayedFocusGain(); } /** * Sets the type of focus gain that will be requested. * Use this method to replace the focus gain when building a request by modifying an * existing {@code AudioFocusRequest} instance. * @param focusGain the type of audio focus gain that will be requested. * @return this {@code Builder} instance * @throws IllegalArgumentException thrown when an invalid focus gain type is used */ public @NonNull Builder setFocusGain(int focusGain) { if (!isValidFocusGain(focusGain)) { throw new IllegalArgumentException("Illegal audio focus gain type " + focusGain); } mFocusGain = focusGain; 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.<br> * Notifications are delivered on the {@link Looper} associated with the one of * the creation of the {@link AudioManager} used to request focus * (see {@link AudioManager#requestAudioFocus(AudioFocusRequest)}). * @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; } /** * @hide * Internal listener setter, no null checks on listener nor handler * @param listener * @param handler * @return this {@code Builder} instance. */ @NonNull Builder setOnAudioFocusChangeListenerInt(OnAudioFocusChangeListener listener, Handler handler) { mFocusListener = listener; mListenerHandler = handler; 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. * @param listener the listener receiving the focus change notifications. * @param handler the {@link Handler} for the thread on which to execute * the notifications. * @return this {@code Builder} instance. * @throws NullPointerException thrown when a null focus listener or handler is used. */ public @NonNull Builder setOnAudioFocusChangeListener(@NonNull OnAudioFocusChangeListener listener, @NonNull Handler handler) { if (listener == null || handler == null) { throw new NullPointerException("Illegal null focus listener or handler"); } mFocusListener = listener; mListenerHandler = handler; return this; } /** * Sets the {@link AudioAttributes} to be associated with the focus request, and which * 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 * playback, see for example {@link MediaPlayer#setAudioAttributes(AudioAttributes)} in * {@code MediaPlayer} or {@link AudioTrack.Builder#setAudioAttributes(AudioAttributes)} * in {@code AudioTrack}. * @param attributes the {@link AudioAttributes} for the focus request. * @return this {@code Builder} instance. * @throws NullPointerException thrown when using null for the attributes. */ public @NonNull Builder setAudioAttributes(@NonNull AudioAttributes attributes) { if (attributes == null) { throw new NullPointerException("Illegal null AudioAttributes"); } mAttr = attributes; return this; } /** * Declare the intended behavior of the application with regards to audio ducking. * See more details in the {@link AudioFocusRequest} class documentation. * @param pauseOnDuck use {@code true} if the application intends to pause audio playback * when losing focus with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}. * If {@code true}, note that you must also set a focus listener to receive such an * event, with * {@link #setOnAudioFocusChangeListener(OnAudioFocusChangeListener, Handler)}. * @return this {@code Builder} instance. */ public @NonNull Builder setWillPauseWhenDucked(boolean pauseOnDuck) { mPausesOnDuck = pauseOnDuck; return this; } /** * Marks this focus request as compatible with delayed focus. * See more details about delayed focus in the {@link AudioFocusRequest} class * documentation. * @param acceptsDelayedFocusGain use {@code true} if the application supports delayed * focus. If {@code true}, note that you must also set a focus listener to be notified * of delayed focus gain, with * {@link #setOnAudioFocusChangeListener(OnAudioFocusChangeListener, Handler)}. * @return this {@code Builder} instance */ public @NonNull Builder setAcceptsDelayedFocusGain(boolean acceptsDelayedFocusGain) { mDelayedFocus = acceptsDelayedFocusGain; return this; } /** * @hide * Marks this focus request as locking audio focus so granting is temporarily disabled. * This feature can only be used by owners of a registered * {@link android.media.audiopolicy.AudioPolicy} in * {@link AudioManager#requestAudioFocus(AudioFocusRequest, android.media.audiopolicy.AudioPolicy)}. * Setting to false is the same as the default behavior. * @param focusLocked true when locking focus * @return this {@code Builder} instance */ @SystemApi public @NonNull Builder setLocksFocus(boolean focusLocked) { mFocusLocked = focusLocked; return this; } /** * Marks this focus request as forcing ducking, regardless of the conditions in which * the system would or would not enforce ducking. * Forcing ducking will only be honored when requesting AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK * with an {@link AudioAttributes} usage of * {@link AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY}, coming from an accessibility * service, and will be ignored otherwise. * @param forceDucking {@code true} to force ducking * @return this {@code Builder} instance */ public @NonNull Builder setForceDucking(boolean forceDucking) { mA11yForceDucking = forceDucking; return this; } /** * Builds a new {@code AudioFocusRequest} instance combining all the information gathered * by this {@code Builder}'s configuration methods. * @return the {@code AudioFocusRequest} instance qualified by all the properties set * on this {@code Builder}. * @throws IllegalStateException thrown when attempting to build a focus request that is set * to accept delayed focus, or to pause on duck, but no focus change listener was set. */ public AudioFocusRequest build() { if ((mDelayedFocus || mPausesOnDuck) && (mFocusListener == null)) { throw new IllegalStateException("Can't use delayed focus or pause on duck without a listener"); } if (mA11yForceDucking) { final Bundle extraInfo; if (mAttr.getBundle() == null) { extraInfo = new Bundle(); } else { extraInfo = mAttr.getBundle(); } // checking of usage and focus request is done server side extraInfo.putBoolean(KEY_ACCESSIBILITY_FORCE_FOCUS_DUCKING, true); mAttr = new AudioAttributes.Builder(mAttr).addBundle(extraInfo).build(); } final int flags = 0 | (mDelayedFocus ? AudioManager.AUDIOFOCUS_FLAG_DELAY_OK : 0) | (mPausesOnDuck ? AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS : 0) | (mFocusLocked ? AudioManager.AUDIOFOCUS_FLAG_LOCK : 0); return new AudioFocusRequest(mFocusListener, mListenerHandler, mAttr, mFocusGain, flags); } } }