Audio focus API with AudioAttributes and more options

Wrap all options of an audio focus request into a new
 class, AudioFocus request, and the corresponding
 methods in AudioManager to request and abandon focus
 with an AudioFocusRequest instance.
New options include handler for focus change listener,
 delayed focus, and option for specifying pause behavior
 on duckable transient loss of focus.

Test: cts-tradefed run cts -m CtsMediaTestCases -t android.media.cts.AudioFocusTest
Bug: 30258418

Change-Id: I99151270d0d9c59595db3f5c91480c7af2d1fd71
diff --git a/api/current.txt b/api/current.txt
index b967feb..3776567 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -20647,6 +20647,26 @@
     field public static final int TYPE_WIRED_HEADSET = 3; // 0x3
   }
 
+  public final class AudioFocusRequest {
+    method public boolean acceptsDelayedFocusGain();
+    method public android.media.AudioAttributes getAudioAttributes();
+    method public int getFocusGain();
+    method public android.media.AudioManager.OnAudioFocusChangeListener getOnAudioFocusChangeListener();
+    method public android.os.Handler getOnAudioFocusChangeListenerHandler();
+    method public boolean willPauseWhenDucked();
+  }
+
+  public static final class AudioFocusRequest.Builder {
+    ctor public AudioFocusRequest.Builder(int);
+    ctor public AudioFocusRequest.Builder(android.media.AudioFocusRequest);
+    method public android.media.AudioFocusRequest build();
+    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, android.os.Handler);
+    method public android.media.AudioFocusRequest.Builder setWillPauseWhenDucked(boolean);
+  }
+
   public final class AudioFormat implements android.os.Parcelable {
     method public int describeContents();
     method public int getChannelCount();
@@ -20723,6 +20743,7 @@
 
   public class AudioManager {
     method public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener);
+    method public int abandonAudioFocusRequest(android.media.AudioFocusRequest);
     method public void adjustStreamVolume(int, int, int);
     method public void adjustSuggestedStreamVolume(int, int, int);
     method public void adjustVolume(int, int);
@@ -20759,6 +20780,7 @@
     method public deprecated void registerRemoteControlClient(android.media.RemoteControlClient);
     method public deprecated boolean registerRemoteController(android.media.RemoteController);
     method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int);
+    method public int requestAudioFocus(android.media.AudioFocusRequest);
     method public deprecated void setBluetoothA2dpOn(boolean);
     method public void setBluetoothScoOn(boolean);
     method public void setMicrophoneMute(boolean);
@@ -20802,6 +20824,7 @@
     field public static final int AUDIOFOCUS_LOSS_TRANSIENT = -2; // 0xfffffffe
     field public static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK = -3; // 0xfffffffd
     field public static final int AUDIOFOCUS_NONE = 0; // 0x0
+    field public static final int AUDIOFOCUS_REQUEST_DELAYED = 2; // 0x2
     field public static final int AUDIOFOCUS_REQUEST_FAILED = 0; // 0x0
     field public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; // 0x1
     field public static final int AUDIO_SESSION_ID_GENERATE = 0; // 0x0
diff --git a/api/system-current.txt b/api/system-current.txt
index d0404d9..e22db03 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -22305,6 +22305,26 @@
     field public static final android.os.Parcelable.Creator<android.media.AudioFocusInfo> CREATOR;
   }
 
+  public final class AudioFocusRequest {
+    method public boolean acceptsDelayedFocusGain();
+    method public android.media.AudioAttributes getAudioAttributes();
+    method public int getFocusGain();
+    method public android.media.AudioManager.OnAudioFocusChangeListener getOnAudioFocusChangeListener();
+    method public android.os.Handler getOnAudioFocusChangeListenerHandler();
+    method public boolean willPauseWhenDucked();
+  }
+
+  public static final class AudioFocusRequest.Builder {
+    ctor public AudioFocusRequest.Builder(int);
+    ctor public AudioFocusRequest.Builder(android.media.AudioFocusRequest);
+    method public android.media.AudioFocusRequest build();
+    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, android.os.Handler);
+    method public android.media.AudioFocusRequest.Builder setWillPauseWhenDucked(boolean);
+  }
+
   public final class AudioFormat implements android.os.Parcelable {
     method public int describeContents();
     method public int getChannelCount();
@@ -22382,6 +22402,7 @@
   public class AudioManager {
     method public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener);
     method public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
+    method public int abandonAudioFocusRequest(android.media.AudioFocusRequest);
     method public void adjustStreamVolume(int, int, int);
     method public void adjustSuggestedStreamVolume(int, int, int);
     method public void adjustVolume(int, int);
@@ -22420,6 +22441,7 @@
     method public deprecated void registerRemoteControlClient(android.media.RemoteControlClient);
     method public deprecated boolean registerRemoteController(android.media.RemoteController);
     method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int);
+    method public int requestAudioFocus(android.media.AudioFocusRequest);
     method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
     method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException;
     method public deprecated void setBluetoothA2dpOn(boolean);
@@ -22469,6 +22491,7 @@
     field public static final int AUDIOFOCUS_LOSS_TRANSIENT = -2; // 0xfffffffe
     field public static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK = -3; // 0xfffffffd
     field public static final int AUDIOFOCUS_NONE = 0; // 0x0
+    field public static final int AUDIOFOCUS_REQUEST_DELAYED = 2; // 0x2
     field public static final int AUDIOFOCUS_REQUEST_FAILED = 0; // 0x0
     field public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; // 0x1
     field public static final int AUDIO_SESSION_ID_GENERATE = 0; // 0x0
diff --git a/api/test-current.txt b/api/test-current.txt
index e6b8010..89db727 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -20743,6 +20743,26 @@
     field public static final int TYPE_WIRED_HEADSET = 3; // 0x3
   }
 
+  public final class AudioFocusRequest {
+    method public boolean acceptsDelayedFocusGain();
+    method public android.media.AudioAttributes getAudioAttributes();
+    method public int getFocusGain();
+    method public android.media.AudioManager.OnAudioFocusChangeListener getOnAudioFocusChangeListener();
+    method public android.os.Handler getOnAudioFocusChangeListenerHandler();
+    method public boolean willPauseWhenDucked();
+  }
+
+  public static final class AudioFocusRequest.Builder {
+    ctor public AudioFocusRequest.Builder(int);
+    ctor public AudioFocusRequest.Builder(android.media.AudioFocusRequest);
+    method public android.media.AudioFocusRequest build();
+    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, android.os.Handler);
+    method public android.media.AudioFocusRequest.Builder setWillPauseWhenDucked(boolean);
+  }
+
   public final class AudioFormat implements android.os.Parcelable {
     method public int describeContents();
     method public int getChannelCount();
@@ -20819,6 +20839,7 @@
 
   public class AudioManager {
     method public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener);
+    method public int abandonAudioFocusRequest(android.media.AudioFocusRequest);
     method public void adjustStreamVolume(int, int, int);
     method public void adjustSuggestedStreamVolume(int, int, int);
     method public void adjustVolume(int, int);
@@ -20855,6 +20876,7 @@
     method public deprecated void registerRemoteControlClient(android.media.RemoteControlClient);
     method public deprecated boolean registerRemoteController(android.media.RemoteController);
     method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int);
+    method public int requestAudioFocus(android.media.AudioFocusRequest);
     method public deprecated void setBluetoothA2dpOn(boolean);
     method public void setBluetoothScoOn(boolean);
     method public void setMicrophoneMute(boolean);
@@ -20898,6 +20920,7 @@
     field public static final int AUDIOFOCUS_LOSS_TRANSIENT = -2; // 0xfffffffe
     field public static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK = -3; // 0xfffffffd
     field public static final int AUDIOFOCUS_NONE = 0; // 0x0
+    field public static final int AUDIOFOCUS_REQUEST_DELAYED = 2; // 0x2
     field public static final int AUDIOFOCUS_REQUEST_FAILED = 0; // 0x0
     field public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; // 0x1
     field public static final int AUDIO_SESSION_ID_GENERATE = 0; // 0x0
diff --git a/media/java/android/media/AudioFocusRequest.java b/media/java/android/media/AudioFocusRequest.java
new file mode 100644
index 0000000..1b75a78
--- /dev/null
+++ b/media/java/android/media/AudioFocusRequest.java
@@ -0,0 +1,321 @@
+/*
+ * 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.media.AudioManager.OnAudioFocusChangeListener;
+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)}.
+ * <p>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.
+ */
+// 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
+    private final static AudioAttributes FOCUS_DEFAULT_ATTR = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_MEDIA).build();
+
+    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;
+
+    //TODO implement use of optional handler
+    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;
+        }
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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;
+    }
+
+    int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * Builder class for {@link AudioFocusRequest} objects.
+     * <p> Here is an example where {@code Builder} is used to define the
+     * {@link AudioFocusRequest} for requesting audio focus:
+     *
+     * <pre class="prettyprint">
+     * mAudioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
+     * mPlaybackAttributes = new AudioAttributes.Builder()
+     *         .setUsage(AudioAttributes.USAGE_GAME)
+     *         .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+     *         .build();
+     * mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+     *         .setAudioAttributes(mPlaybackAttributes)
+     *         .setAcceptsDelayedFocusGain(true)
+     *         .setOnAudioFocusChangeListener(mMyFocusListener, mMyHandler)
+     *         .build();
+     * mMediaPlayer = new MediaPlayer();
+     *  ...
+     * mMediaPlayer.setAudioAttributes(mPlaybackAttributes);
+     *  ...
+     * mAudioManager.requestAudioFocus(mFocusRequest);
+     *  ...
+     * mAudioManager.abandonAudioFocusRequest(mFocusRequest);
+     * </pre>
+     *
+     */
+    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;
+
+        /**
+         * 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, 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.
+         * @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.
+         * @return this {@code Builder} instance.
+         * @throws IllegalArgumentException thrown when a non-null handler is used with a null
+         *   listener.
+         */
+        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");
+            }
+            mFocusListener = listener;
+            mListenerHandler = handler;
+            return this;
+        }
+
+        /**
+         * Sets the {@link AudioAttributes} to be associated with the focus request, and which
+         * describe the use case describing why 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 IllegalArgumentException thrown when using null for the attributes.
+         */
+        public @NonNull Builder setAudioAttributes(@NonNull AudioAttributes attributes) {
+            if (attributes == null) {
+                throw new IllegalArgumentException("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;
+        }
+
+        /**
+         * 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 IllegalArgumentException thrown when focus request 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 IllegalArgumentException(
+                        "Can't use delayed focus or pause on duck without a listener");
+            }
+            final int flags = 0
+                    | (mDelayedFocus ? AudioManager.AUDIOFOCUS_FLAG_DELAY_OK : 0)
+                    | (mPausesOnDuck ? AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS : 0);
+            return new AudioFocusRequest(mFocusListener, mListenerHandler,
+                    mAttr, mFocusGain, flags);
+        }
+    }
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 95d354b..dc69a69 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2209,11 +2209,11 @@
      */
     public static final int AUDIOFOCUS_REQUEST_GRANTED = 1;
      /**
-      * @hide
       * A focus change request whose granting is delayed: the request was successful, but the
       * requester will only be granted audio focus once the condition that prevented immediate
       * granting has ended.
-      * See {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
+      * See {@link #requestAudioFocus(AudioFocusRequest)} and
+      * {@link AudioFocusRequest.Builder#setAcceptsDelayedFocusGain(boolean)}
       */
     public static final int AUDIOFOCUS_REQUEST_DELAYED = 2;
 
@@ -2293,6 +2293,44 @@
             | AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS | AUDIOFOCUS_FLAG_LOCK;
 
     /**
+     * Request audio focus.
+     * See the {@link AudioFocusRequest} for information about the options available to configure
+     * your request, and notification of focus gain and loss.
+     * @param focusRequest a {@link AudioFocusRequest} instance used to configure how focus is
+     *   requested.
+     * @return {@link #AUDIOFOCUS_REQUEST_FAILED}, {@link #AUDIOFOCUS_REQUEST_GRANTED}
+     *     or {@link #AUDIOFOCUS_REQUEST_DELAYED}.
+     *     <br>Note that the return value is never {@link #AUDIOFOCUS_REQUEST_DELAYED} when focus
+     *     is requested without building the {@link AudioFocusRequest} with
+     *     {@link AudioFocusRequest.Builder#setAcceptsDelayedFocusGain(boolean)} set to
+     *     {@code true}.
+     * @throws IllegalArgumentException if passed a null argument
+     */
+    public int requestAudioFocus(@NonNull AudioFocusRequest focusRequest) {
+        if (focusRequest == null) {
+            throw new IllegalArgumentException("Illegal null AudioFocusRequest");
+        }
+        return requestAudioFocus(focusRequest.getOnAudioFocusChangeListener(),
+                focusRequest.getAudioAttributes(),
+                focusRequest.getFocusGain(), focusRequest.getFlags(), null /* no AudioPolicy*/);
+    }
+
+    /**
+     *  Abandon audio focus. Causes the previous focus owner, if any, to receive focus.
+     *  @param focusRequest the {@link AudioFocusRequest} that was used when requesting focus
+     *      with {@link #requestAudioFocus(AudioFocusRequest)}.
+     *  @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED}
+     *  @throws IllegalArgumentException if passed a null argument
+     */
+    public int abandonAudioFocusRequest(@NonNull AudioFocusRequest focusRequest) {
+        if (focusRequest == null) {
+            throw new IllegalArgumentException("Illegal null AudioFocusRequest");
+        }
+        return abandonAudioFocus(focusRequest.getOnAudioFocusChangeListener(),
+                focusRequest.getAudioAttributes());
+    }
+
+    /**
      * @hide
      * Request audio focus.
      * Send a request to obtain the audio focus. This method differs from
@@ -2372,8 +2410,7 @@
         if (requestAttributes == null) {
             throw new IllegalArgumentException("Illegal null AudioAttributes argument");
         }
-        if ((durationHint < AUDIOFOCUS_GAIN) ||
-                (durationHint > AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
+        if (!AudioFocusRequest.isValidFocusGain(durationHint)) {
             throw new IllegalArgumentException("Invalid duration hint");
         }
         if (flags != (flags & AUDIOFOCUS_FLAGS_SYSTEM)) {