summaryrefslogtreecommitdiff
path: root/android/media/AudioFocusRequest.java
blob: 7104dad4dc4c929ce38850d9215e937d0f847951 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
/*
 * 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.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. Let’s 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 doesn’t 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 didn’t 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,
 * it’s 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 don’t 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
 * &#64;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.
     */
    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 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;
        }

        /**
         * @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);
        }
    }
}