Merge "Support collaborative audio focus handling" into lmp-mr1-dev
diff --git a/Android.mk b/Android.mk
index 1416472..dd564557 100644
--- a/Android.mk
+++ b/Android.mk
@@ -330,6 +330,7 @@
 	media/java/android/media/IRemoteVolumeObserver.aidl \
 	media/java/android/media/IRingtonePlayer.aidl \
 	media/java/android/media/IVolumeController.aidl \
+        media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl \
 	media/java/android/media/projection/IMediaProjection.aidl \
 	media/java/android/media/projection/IMediaProjectionCallback.aidl \
 	media/java/android/media/projection/IMediaProjectionManager.aidl \
@@ -437,6 +438,7 @@
 	frameworks/base/media/java/android/media/MediaDescription.aidl \
 	frameworks/base/media/java/android/media/Rating.aidl \
 	frameworks/base/media/java/android/media/AudioAttributes.aidl \
+	frameworks/base/media/java/android/media/AudioFocusInfo.aidl \
 	frameworks/base/media/java/android/media/session/PlaybackState.aidl \
 	frameworks/base/media/java/android/media/session/MediaSession.aidl \
 	frameworks/base/media/java/android/media/tv/TvInputInfo.aidl \
diff --git a/media/java/android/media/AudioFocusInfo.aidl b/media/java/android/media/AudioFocusInfo.aidl
new file mode 100644
index 0000000..f925fda
--- /dev/null
+++ b/media/java/android/media/AudioFocusInfo.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2014, 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;
+
+parcelable AudioFocusInfo;
diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java
new file mode 100644
index 0000000..fbdda3c
--- /dev/null
+++ b/media/java/android/media/AudioFocusInfo.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2014 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.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ * A class to encapsulate information about an audio focus owner or request.
+ */
+@SystemApi
+public final class AudioFocusInfo implements Parcelable {
+
+    private AudioAttributes mAttributes;
+    private String mClientId;
+    private String mPackageName;
+    private int mGainRequest;
+    private int mLossReceived;
+    private int mFlags;
+
+
+    /**
+     * Class constructor
+     * @param aa
+     * @param clientId
+     * @param packageName
+     * @param gainRequest
+     * @param lossReceived
+     * @param flags
+     */
+    AudioFocusInfo(AudioAttributes aa, String clientId, String packageName,
+            int gainRequest, int lossReceived, int flags) {
+        mAttributes = aa == null ? new AudioAttributes.Builder().build() : aa;
+        mClientId = clientId == null ? "" : clientId;
+        mPackageName = packageName == null ? "" : packageName;
+        mGainRequest = gainRequest;
+        mLossReceived = lossReceived;
+        mFlags = flags;
+    }
+
+
+    /**
+     * The audio attributes for the audio focus request.
+     * @return non-null {@link AudioAttributes}.
+     */
+    @SystemApi
+    public AudioAttributes getAttributes() { return mAttributes; }
+
+    @SystemApi
+    public String getClientId() { return mClientId; }
+
+    @SystemApi
+    public String getPackageName() { return mPackageName; }
+
+    /**
+     * The type of audio focus gain request.
+     * @return one of {@link AudioManager#AUDIOFOCUS_GAIN},
+     *     {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT},
+     *     {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK},
+     *     {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}.
+     */
+    @SystemApi
+    public int getGainRequest() { return mGainRequest; }
+
+    /**
+     * The type of audio focus loss that was received by the
+     * {@link AudioManager.OnAudioFocusChangeListener} if one was set.
+     * @return 0 if focus wasn't lost, or one of {@link AudioManager#AUDIOFOCUS_LOSS},
+     *   {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT} or
+     *   {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
+     */
+    @SystemApi
+    public int getLossReceived() { return mLossReceived; }
+
+    /** @hide */
+    void clearLossReceived() { mLossReceived = 0; }
+
+    /**
+     * The flags set in the audio focus request.
+     * @return 0 or a combination of {link AudioManager#AUDIOFOCUS_FLAG_DELAY_OK},
+     *     {@link AudioManager#AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS}, and
+     *     {@link AudioManager#AUDIOFOCUS_FLAG_LOCK}.
+     */
+    @SystemApi
+    public int getFlags() { return mFlags; }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        mAttributes.writeToParcel(dest, flags);
+        dest.writeString(mClientId);
+        dest.writeString(mPackageName);
+        dest.writeInt(mGainRequest);
+        dest.writeInt(mLossReceived);
+        dest.writeInt(mFlags);
+    }
+
+    @SystemApi
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAttributes, mClientId, mPackageName, mGainRequest, mFlags);
+    }
+
+    @SystemApi
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        AudioFocusInfo other = (AudioFocusInfo) obj;
+        if (!mAttributes.equals(other.mAttributes)) {
+            return false;
+        }
+        if (!mClientId.equals(other.mClientId)) {
+            return false;
+        }
+        if (!mPackageName.equals(other.mPackageName)) {
+            return false;
+        }
+        if (mGainRequest != other.mGainRequest) {
+            return false;
+        }
+        if (mLossReceived != other.mLossReceived) {
+            return false;
+        }
+        if (mFlags != other.mFlags) {
+            return false;
+        }
+        return true;
+    }
+
+    public static final Parcelable.Creator<AudioFocusInfo> CREATOR
+            = new Parcelable.Creator<AudioFocusInfo>() {
+
+        public AudioFocusInfo createFromParcel(Parcel in) {
+            return new AudioFocusInfo(
+                    AudioAttributes.CREATOR.createFromParcel(in), //AudioAttributes aa
+                    in.readString(), //String clientId
+                    in.readString(), //String packageName
+                    in.readInt(), //int gainRequest
+                    in.readInt(), //int lossReceived
+                    in.readInt() //int flags
+                    );
+        }
+
+        public AudioFocusInfo[] newArray(int size) {
+            return new AudioFocusInfo[size];
+        }
+    };
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index ee9044e..360f764 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2385,17 +2385,42 @@
     }
 
     // when adding new flags, add them to the relevant AUDIOFOCUS_FLAGS_APPS or SYSTEM masks
-    /** @hide */
+    /**
+     * @hide
+     * Use this flag when requesting audio focus to indicate it is ok for the requester to not be
+     * granted audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when
+     * the system is in a state where focus cannot change, but be granted focus later when
+     * this condition ends.
+     */
     @SystemApi
     public static final int AUDIOFOCUS_FLAG_DELAY_OK = 0x1 << 0;
-    /** @hide */
+    /**
+     * @hide
+     * Use this flag when requesting audio focus to indicate that the requester
+     * will pause its media playback (if applicable) when losing audio focus with
+     * {@link #AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}, rather than ducking.
+     * <br>On some platforms, the ducking may be handled without the application being aware of it
+     * (i.e. it will not transiently lose focus). For applications that for instance play spoken
+     * content, such as audio book or podcast players, ducking may never be acceptable, and will
+     * thus always pause. This flag enables them to be declared as such whenever they request focus.
+     */
     @SystemApi
-    public static final int AUDIOFOCUS_FLAG_LOCK     = 0x1 << 1;
+    public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 0x1 << 1;
+    /**
+     * @hide
+     * Use this flag to lock audio focus so granting is temporarily disabled.
+     * <br>This flag can only be used by owners of a registered
+     * {@link android.media.audiopolicy.AudioPolicy} in
+     * {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int, AudioPolicy)}
+     */
+    @SystemApi
+    public static final int AUDIOFOCUS_FLAG_LOCK     = 0x1 << 2;
     /** @hide */
-    public static final int AUDIOFOCUS_FLAGS_APPS = AUDIOFOCUS_FLAG_DELAY_OK;
+    public static final int AUDIOFOCUS_FLAGS_APPS = AUDIOFOCUS_FLAG_DELAY_OK
+            | AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS;
     /** @hide */
     public static final int AUDIOFOCUS_FLAGS_SYSTEM = AUDIOFOCUS_FLAG_DELAY_OK
-            | AUDIOFOCUS_FLAG_LOCK;
+            | AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS | AUDIOFOCUS_FLAG_LOCK;
 
     /**
      * @hide
@@ -2417,15 +2442,12 @@
      *      usecases such as voice memo recording, or speech recognition.
      *      Use {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such
      *      as the playback of a song or a video.
-     * @param flags 0 or {link #AUDIOFOCUS_FLAG_DELAY_OK}.
+     * @param flags 0 or a combination of {link #AUDIOFOCUS_FLAG_DELAY_OK}
+     *     and {@link #AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS}.
      *     <br>Use 0 when not using any flags for the request, which behaves like
      *     {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio
      *     focus is granted immediately, or the grant request fails because the system is in a
      *     state where focus cannot change (e.g. a phone call).
-     *     <br>Use {link #AUDIOFOCUS_FLAG_DELAY_OK} if it is ok for the requester to not be granted
-     *     audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when
-     *     the system is in a state where focus cannot change, but be granted focus later when
-     *     this condition ends.
      * @return {@link #AUDIOFOCUS_REQUEST_FAILED}, {@link #AUDIOFOCUS_REQUEST_GRANTED}
      *     or {@link #AUDIOFOCUS_REQUEST_DELAYED}.
      *     The return value is never {@link #AUDIOFOCUS_REQUEST_DELAYED} when focus is requested
@@ -2459,17 +2481,11 @@
      * @param durationHint see the description of the same parameter in
      *     {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
      * @param flags 0 or a combination of {link #AUDIOFOCUS_FLAG_DELAY_OK},
-     *     {@link #AUDIOFOCUS_FLAG_LOCK}
+     *     {@link #AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS}, and {@link #AUDIOFOCUS_FLAG_LOCK}.
      *     <br>Use 0 when not using any flags for the request, which behaves like
      *     {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio
      *     focus is granted immediately, or the grant request fails because the system is in a
      *     state where focus cannot change (e.g. a phone call).
-     *     <br>Use {link #AUDIOFOCUS_FLAG_DELAY_OK} if it is ok for the requester to not be granted
-     *     audio focus immediately (as indicated by {@link #AUDIOFOCUS_REQUEST_DELAYED}) when
-     *     the system is in a state where focus cannot change, but be granted focus later when
-     *     this condition ends.
-     *     <br>Use {@link #AUDIOFOCUS_FLAG_LOCK} when locking audio focus so granting is
-     *     temporarily disabled.
      * @param ap a registered {@link android.media.audiopolicy.AudioPolicy} instance when locking
      *     focus, or null.
      * @return see the description of the same return value in
@@ -2510,7 +2526,7 @@
             status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
                     mAudioFocusDispatcher, getIdForAudioFocusListener(l),
                     mContext.getOpPackageName() /* package name */, flags,
-                    ap != null ? ap.token() : null);
+                    ap != null ? ap.cb() : null);
         } catch (RemoteException e) {
             Log.e(TAG, "Can't call requestAudioFocus() on AudioService:", e);
         }
@@ -2887,7 +2903,8 @@
         }
         IAudioService service = getService();
         try {
-            String regId = service.registerAudioPolicy(policy.getConfig(), policy.token());
+            String regId = service.registerAudioPolicy(policy.getConfig(), policy.cb(),
+                    policy.hasFocusListener());
             if (regId == null) {
                 return ERROR;
             } else {
@@ -2912,7 +2929,7 @@
         }
         IAudioService service = getService();
         try {
-            service.unregisterAudioPolicyAsync(policy.token());
+            service.unregisterAudioPolicyAsync(policy.cb());
             policy.setRegistration(null);
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e);
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index f1d6a0a..03ecad2 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -51,6 +51,7 @@
 import android.media.audiopolicy.AudioMix;
 import android.media.audiopolicy.AudioPolicy;
 import android.media.audiopolicy.AudioPolicyConfig;
+import android.media.audiopolicy.IAudioPolicyCallback;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Environment;
@@ -5098,7 +5099,7 @@
     //==========================================================================================
     public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
             IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
-            IBinder policyToken) {
+            IAudioPolicyCallback pcb) {
         // permission checks
         if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {
             if (mMediaFocusControl.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) {
@@ -5110,9 +5111,8 @@
             } else {
                 // only a registered audio policy can be used to lock focus
                 synchronized (mAudioPolicies) {
-                    if (!mAudioPolicies.containsKey(policyToken)) {
-                        Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus",
-                                new Exception());
+                    if (!mAudioPolicies.containsKey(pcb.asBinder())) {
+                        Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus");
                         return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                     }
                 }
@@ -5812,30 +5812,34 @@
     //==========================================================================================
     // Audio policy management
     //==========================================================================================
-    public String registerAudioPolicy(AudioPolicyConfig policyConfig, IBinder cb) {
-        //Log.v(TAG, "registerAudioPolicy for " + cb + " got policy:" + policyConfig);
+    public String registerAudioPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb,
+            boolean hasFocusListener) {
+        if (DEBUG_AP) Log.d(TAG, "registerAudioPolicy for " + pcb.asBinder()
+                + " with config:" + policyConfig);
         String regId = null;
+        // error handling
         boolean hasPermissionForPolicy =
-                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
+                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
                         android.Manifest.permission.MODIFY_AUDIO_ROUTING));
         if (!hasPermissionForPolicy) {
             Slog.w(TAG, "Can't register audio policy for pid " + Binder.getCallingPid() + " / uid "
                     + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING");
             return null;
         }
+
         synchronized (mAudioPolicies) {
             try {
-                if (mAudioPolicies.containsKey(cb)) {
+                if (mAudioPolicies.containsKey(pcb.asBinder())) {
                     Slog.e(TAG, "Cannot re-register policy");
                     return null;
                 }
-                AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
-                cb.linkToDeath(app, 0/*flags*/);
-                regId = app.connectMixes();
-                mAudioPolicies.put(cb, app);
+                AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener);
+                pcb.asBinder().linkToDeath(app, 0/*flags*/);
+                regId = app.getRegistrationId();
+                mAudioPolicies.put(pcb.asBinder(), app);
             } catch (RemoteException e) {
                 // audio policy owner has already died!
-                Slog.w(TAG, "Audio policy registration failed, could not link to " + cb +
+                Slog.w(TAG, "Audio policy registration failed, could not link to " + pcb +
                         " binder death", e);
                 return null;
             }
@@ -5843,21 +5847,58 @@
         return regId;
     }
 
-    public void unregisterAudioPolicyAsync(IBinder cb) {
+    public void unregisterAudioPolicyAsync(IAudioPolicyCallback pcb) {
+        if (DEBUG_AP) Log.d(TAG, "unregisterAudioPolicyAsync for " + pcb.asBinder());
         synchronized (mAudioPolicies) {
-            AudioPolicyProxy app = mAudioPolicies.remove(cb);
+            AudioPolicyProxy app = mAudioPolicies.remove(pcb.asBinder());
             if (app == null) {
                 Slog.w(TAG, "Trying to unregister unknown audio policy for pid "
                         + Binder.getCallingPid() + " / uid " + Binder.getCallingUid());
                 return;
             } else {
-                cb.unlinkToDeath(app, 0/*flags*/);
+                pcb.asBinder().unlinkToDeath(app, 0/*flags*/);
             }
-            app.disconnectMixes();
+            app.release();
         }
         // TODO implement clearing mix attribute matching info in native audio policy
     }
 
+    public int setFocusPropertiesForPolicy(int duckingBehavior, IAudioPolicyCallback pcb) {
+        if (DEBUG_AP) Log.d(TAG, "setFocusPropertiesForPolicy() duck behavior=" + duckingBehavior
+                + " policy " +  pcb.asBinder());
+        // error handling
+        boolean hasPermissionForPolicy =
+                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
+                        android.Manifest.permission.MODIFY_AUDIO_ROUTING));
+        if (!hasPermissionForPolicy) {
+            Slog.w(TAG, "Cannot change audio policy ducking handling for pid " +
+                    + Binder.getCallingPid() + " / uid "
+                    + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING");
+            return AudioManager.ERROR;
+        }
+
+        synchronized (mAudioPolicies) {
+            if (!mAudioPolicies.containsKey(pcb.asBinder())) {
+                Slog.e(TAG, "Cannot change audio policy focus properties, unregistered policy");
+                return AudioManager.ERROR;
+            }
+            final AudioPolicyProxy app = mAudioPolicies.get(pcb.asBinder());
+            if (duckingBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) {
+                // is there already one policy managing ducking?
+                for(AudioPolicyProxy policy : mAudioPolicies.values()) {
+                    if (policy.mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) {
+                        Slog.e(TAG, "Cannot change audio policy ducking behavior, already handled");
+                        return AudioManager.ERROR;
+                    }
+                }
+            }
+            app.mFocusDuckBehavior = duckingBehavior;
+            mMediaFocusControl.setDuckingInExtPolicyAvailable(
+                    duckingBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY);
+        }
+        return AudioManager.SUCCESS;
+    }
+
     private void dumpAudioPolicies(PrintWriter pw) {
         pw.println("\nAudio policies:");
         synchronized (mAudioPolicies) {
@@ -5877,27 +5918,48 @@
     public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient {
         private static final String TAG = "AudioPolicyProxy";
         AudioPolicyConfig mConfig;
-        IBinder mToken;
-        AudioPolicyProxy(AudioPolicyConfig config, IBinder token) {
+        IAudioPolicyCallback mPolicyToken;
+        boolean mHasFocusListener;
+        /**
+         * Audio focus ducking behavior for an audio policy.
+         * This variable reflects the value that was successfully set in
+         * {@link AudioService#setFocusPropertiesForPolicy(int, IAudioPolicyCallback)}. This
+         * implies that a value of FOCUS_POLICY_DUCKING_IN_POLICY means the corresponding policy
+         * is handling ducking for audio focus.
+         */
+        int mFocusDuckBehavior = AudioPolicy.FOCUS_POLICY_DUCKING_DEFAULT;
+
+        AudioPolicyProxy(AudioPolicyConfig config, IAudioPolicyCallback token,
+                boolean hasFocusListener) {
             super(config);
             setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++));
-            mToken = token;
+            mPolicyToken = token;
+            mHasFocusListener = hasFocusListener;
+            if (mHasFocusListener) {
+                mMediaFocusControl.addFocusFollower(mPolicyToken);
+            }
+            updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE);
         }
 
         public void binderDied() {
             synchronized (mAudioPolicies) {
-                Log.i(TAG, "audio policy " + mToken + " died");
-                disconnectMixes();
-                mAudioPolicies.remove(mToken);
+                Log.i(TAG, "audio policy " + mPolicyToken + " died");
+                release();
+                mAudioPolicies.remove(mPolicyToken.asBinder());
             }
         }
 
-        String connectMixes() {
-            updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE);
+        String getRegistrationId() {
             return getRegistration();
         }
 
-        void disconnectMixes() {
+        void release() {
+            if (mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) {
+                mMediaFocusControl.setDuckingInExtPolicyAvailable(false);
+            }
+            if (mHasFocusListener) {
+                mMediaFocusControl.removeFocusFollower(mPolicyToken);
+            }
             updateMixes(AudioSystem.DEVICE_STATE_UNAVAILABLE);
         }
 
diff --git a/media/java/android/media/FocusRequester.java b/media/java/android/media/FocusRequester.java
index 0d675fc..bbe5fd2 100644
--- a/media/java/android/media/FocusRequester.java
+++ b/media/java/android/media/FocusRequester.java
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import android.annotation.NonNull;
 import android.media.MediaFocusControl.AudioFocusDeathHandler;
 import android.os.IBinder;
 import android.util.Log;
@@ -40,6 +41,7 @@
     private final String mClientId;
     private final String mPackageName;
     private final int mCallingUid;
+    private final MediaFocusControl mFocusController; // never null
     /**
      * the audio focus gain request that caused the addition of this object in the focus stack.
      */
@@ -59,9 +61,22 @@
      */
     private final AudioAttributes mAttributes;
 
+    /**
+     * Class constructor
+     * @param aa
+     * @param focusRequest
+     * @param grantFlags
+     * @param afl
+     * @param source
+     * @param id
+     * @param hdlr
+     * @param pn
+     * @param uid
+     * @param ctlr cannot be null
+     */
     FocusRequester(AudioAttributes aa, int focusRequest, int grantFlags,
             IAudioFocusDispatcher afl, IBinder source, String id, AudioFocusDeathHandler hdlr,
-            String pn, int uid) {
+            String pn, int uid, @NonNull MediaFocusControl ctlr) {
         mAttributes = aa;
         mFocusDispatcher = afl;
         mSourceRef = source;
@@ -72,6 +87,7 @@
         mFocusGainRequest = focusRequest;
         mGrantFlags = grantFlags;
         mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
+        mFocusController = ctlr;
     }
 
 
@@ -153,9 +169,17 @@
 
     private static String flagsToString(int flags) {
         String msg = new String();
-        if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) != 0) { msg += "DELAY_OK"; }
-        if (!msg.isEmpty()) { msg += "|"; }
-        if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0)     { msg += "LOCK"; }
+        if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) != 0) {
+            msg += "DELAY_OK";
+        }
+        if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0)     {
+            if (!msg.isEmpty()) { msg += "|"; }
+            msg += "LOCK";
+        }
+        if ((flags & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0) {
+            if (!msg.isEmpty()) { msg += "|"; }
+            msg += "PAUSES_ON_DUCKABLE_LOSS";
+        }
         return msg;
     }
 
@@ -230,13 +254,22 @@
         }
     }
 
+    /**
+     * Called synchronized on MediaFocusControl.mAudioFocusLock
+     */
     void handleExternalFocusGain(int focusGain) {
         int focusLoss = focusLossForGainRequest(focusGain);
         handleFocusLoss(focusLoss);
     }
 
+    /**
+     * Called synchronized on MediaFocusControl.mAudioFocusLock
+     */
     void handleFocusGain(int focusGain) {
         try {
+            mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
+            mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(),
+                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
             if (mFocusDispatcher != null) {
                 if (DEBUG) {
                     Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
@@ -244,27 +277,52 @@
                 }
                 mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId);
             }
-            mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
         } catch (android.os.RemoteException e) {
             Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
         }
     }
 
+    /**
+     * Called synchronized on MediaFocusControl.mAudioFocusLock
+     */
     void handleFocusLoss(int focusLoss) {
         try {
             if (focusLoss != mFocusLossReceived) {
+                mFocusLossReceived = focusLoss;
+                // before dispatching a focus loss, check if the following conditions are met:
+                // 1/ the framework is not supposed to notify the focus loser on a DUCK loss
+                // 2/ it is a DUCK loss
+                // 3/ the focus loser isn't flagged as pausing in a DUCK loss
+                // if they are, do not notify the focus loser
+                if (!mFocusController.mustNotifyFocusOwnerOnDuck()
+                        && mFocusLossReceived == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
+                        && (mGrantFlags
+                                & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) == 0) {
+                    if (DEBUG) {
+                        Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
+                                + " to " + mClientId + ", to be handled externally");
+                    }
+                    mFocusController.notifyExtPolicyFocusLoss_syncAf(
+                            toAudioFocusInfo(), false /* wasDispatched */);
+                    return;
+                }
                 if (mFocusDispatcher != null) {
                     if (DEBUG) {
-                        Log.v(TAG, "dispatching " + focusChangeToString(focusLoss) + " to "
+                        Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "
                             + mClientId);
                     }
-                    mFocusDispatcher.dispatchAudioFocusChange(focusLoss, mClientId);
+                    mFocusController.notifyExtPolicyFocusLoss_syncAf(
+                            toAudioFocusInfo(), true /* wasDispatched */);
+                    mFocusDispatcher.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
                 }
-                mFocusLossReceived = focusLoss;
             }
         } catch (android.os.RemoteException e) {
             Log.e(TAG, "Failure to signal loss of audio focus due to:", e);
         }
     }
 
+    AudioFocusInfo toAudioFocusInfo() {
+        return new AudioFocusInfo(mAttributes, mClientId, mPackageName,
+                mFocusGainRequest, mFocusLossReceived, mGrantFlags);
+    }
 }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 4d8aa76..fad3cec 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -30,6 +30,7 @@
 import android.media.IVolumeController;
 import android.media.Rating;
 import android.media.audiopolicy.AudioPolicyConfig;
+import android.media.audiopolicy.IAudioPolicyCallback;
 import android.net.Uri;
 import android.view.KeyEvent;
 
@@ -123,7 +124,7 @@
 
     int requestAudioFocus(in AudioAttributes aa, int durationHint, IBinder cb,
             IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
-            IBinder policyToken);
+            IAudioPolicyCallback pcb);
 
     int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, in AudioAttributes aa);
 
@@ -213,6 +214,9 @@
 
     boolean isHdmiSystemAudioSupported();
 
-           String registerAudioPolicy(in AudioPolicyConfig policyConfig, IBinder cb);
-    oneway void unregisterAudioPolicyAsync(in IBinder cb);
+           String registerAudioPolicy(in AudioPolicyConfig policyConfig,
+                    in IAudioPolicyCallback pcb, boolean hasFocusListener);
+    oneway void unregisterAudioPolicyAsync(in IAudioPolicyCallback pcb);
+
+           int setFocusPropertiesForPolicy(int duckingBehavior, in IAudioPolicyCallback pcb);
 }
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index 8d96970..6518bd1 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -33,6 +33,7 @@
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.media.PlayerRecord.RemotePlaybackState;
+import android.media.audiopolicy.IAudioPolicyCallback;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -434,6 +435,9 @@
         }
     }
 
+    /**
+     * Called synchronized on mAudioFocusLock
+     */
     private void notifyTopOfAudioFocusStack() {
         // notify the top of the stack it gained focus
         if (!mFocusStack.empty()) {
@@ -470,6 +474,7 @@
                 stackIterator.next().dump(pw);
             }
         }
+        pw.println("\n Notify on duck: " + mNotifyFocusOwnerOnDuck +"\n");
     }
 
     /**
@@ -480,13 +485,19 @@
      * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding
      *   focus, notify the next item in the stack it gained focus.
      */
-    private void removeFocusStackEntry(String clientToRemove, boolean signal) {
+    private void removeFocusStackEntry(String clientToRemove, boolean signal,
+            boolean notifyFocusFollowers) {
         // is the current top of the focus stack abandoning focus? (because of request, not death)
         if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
         {
             //Log.i(TAG, "   removeFocusStackEntry() removing top of stack");
             FocusRequester fr = mFocusStack.pop();
             fr.release();
+            if (notifyFocusFollowers) {
+                final AudioFocusInfo afi = fr.toAudioFocusInfo();
+                afi.clearLossReceived();
+                notifyExtPolicyFocusLoss_syncAf(afi, false);
+            }
             if (signal) {
                 // notify the new top of the stack it gained focus
                 notifyTopOfAudioFocusStack();
@@ -610,6 +621,86 @@
         }
     }
 
+    /**
+     * Indicates whether to notify an audio focus owner when it loses focus
+     * with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} if it will only duck.
+     * This variable being false indicates an AudioPolicy has been registered and has signaled
+     * it will handle audio ducking.
+     */
+    private boolean mNotifyFocusOwnerOnDuck = true;
+
+    protected void setDuckingInExtPolicyAvailable(boolean available) {
+        mNotifyFocusOwnerOnDuck = !available;
+    }
+
+    boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; }
+
+    private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>();
+
+    void addFocusFollower(IAudioPolicyCallback ff) {
+        if (ff == null) {
+            return;
+        }
+        synchronized(mAudioFocusLock) {
+            boolean found = false;
+            for (IAudioPolicyCallback pcb : mFocusFollowers) {
+                if (pcb.asBinder().equals(ff.asBinder())) {
+                    found = true;
+                    break;
+                }
+            }
+            if (found) {
+                return;
+            } else {
+                mFocusFollowers.add(ff);
+            }
+        }
+    }
+
+    void removeFocusFollower(IAudioPolicyCallback ff) {
+        if (ff == null) {
+            return;
+        }
+        synchronized(mAudioFocusLock) {
+            for (IAudioPolicyCallback pcb : mFocusFollowers) {
+                if (pcb.asBinder().equals(ff.asBinder())) {
+                    mFocusFollowers.remove(pcb);
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Called synchronized on mAudioFocusLock
+     */
+    void notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult) {
+        for (IAudioPolicyCallback pcb : mFocusFollowers) {
+            try {
+                // oneway
+                pcb.notifyAudioFocusGrant(afi, requestResult);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Can't call newAudioFocusLoser() on IAudioPolicyCallback "
+                        + pcb.asBinder(), e);
+            }
+        }
+    }
+
+    /**
+     * Called synchronized on mAudioFocusLock
+     */
+    void notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched) {
+        for (IAudioPolicyCallback pcb : mFocusFollowers) {
+            try {
+                // oneway
+                pcb.notifyAudioFocusLoss(afi, wasDispatched);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Can't call newAudioFocusLoser() on IAudioPolicyCallback "
+                        + pcb.asBinder(), e);
+            }
+        }
+    }
+
     protected int getCurrentAudioFocus() {
         synchronized(mAudioFocusLock) {
             if (mFocusStack.empty()) {
@@ -669,6 +760,8 @@
                     // unlink death handler so it can be gc'ed.
                     // linkToDeath() creates a JNI global reference preventing collection.
                     cb.unlinkToDeath(afdh, 0);
+                    notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
+                            AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                     return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                 }
                 // the reason for the audio focus request has changed: remove the current top of
@@ -681,14 +774,18 @@
             }
 
             // focus requester might already be somewhere below in the stack, remove it
-            removeFocusStackEntry(clientId, false /* signal */);
+            removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
 
             final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
-                    clientId, afdh, callingPackageName, Binder.getCallingUid());
+                    clientId, afdh, callingPackageName, Binder.getCallingUid(), this);
             if (focusGrantDelayed) {
                 // focusGrantDelayed being true implies we can't reassign focus right now
                 // which implies the focus stack is not empty.
-                return pushBelowLockedFocusOwners(nfr);
+                final int requestResult = pushBelowLockedFocusOwners(nfr);
+                if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
+                    notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
+                }
+                return requestResult;
             } else {
                 // propagate the focus change through the stack
                 if (!mFocusStack.empty()) {
@@ -698,6 +795,8 @@
                 // push focus requester at the top of the audio focus stack
                 mFocusStack.push(nfr);
             }
+            notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
+                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
 
         }//synchronized(mAudioFocusLock)
 
@@ -713,7 +812,7 @@
         try {
             // this will take care of notifying the new focus owner if needed
             synchronized(mAudioFocusLock) {
-                removeFocusStackEntry(clientId, true /*signal*/);
+                removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
             }
         } catch (java.util.ConcurrentModificationException cme) {
             // Catching this exception here is temporary. It is here just to prevent
@@ -729,7 +828,7 @@
 
     protected void unregisterAudioFocusClient(String clientId) {
         synchronized(mAudioFocusLock) {
-            removeFocusStackEntry(clientId, false);
+            removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/);
         }
     }
 
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 44d2430..f128044 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -22,16 +22,20 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
+import android.media.AudioFocusInfo;
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioRecord;
 import android.media.AudioTrack;
+import android.media.IAudioService;
 import android.media.MediaRecorder;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.util.Log;
 import android.util.Slog;
 
@@ -47,6 +51,8 @@
 public class AudioPolicy {
 
     private static final String TAG = "AudioPolicy";
+    private static final boolean DEBUG = false;
+    private final Object mLock = new Object();
 
     /**
      * The status of an audio policy that is valid but cannot be used because it is not registered.
@@ -63,19 +69,39 @@
     private String mRegistrationId;
     private AudioPolicyStatusListener mStatusListener;
 
-    private final IBinder mToken = new Binder();
-    /** @hide */
-    public IBinder token() { return mToken; }
+    /**
+     * The behavior of a policy with regards to audio focus where it relies on the application
+     * to do the ducking, the is the legacy and default behavior.
+     */
+    @SystemApi
+    public static final int FOCUS_POLICY_DUCKING_IN_APP = 0;
+    public static final int FOCUS_POLICY_DUCKING_DEFAULT = FOCUS_POLICY_DUCKING_IN_APP;
+    /**
+     * The behavior of a policy with regards to audio focus where it handles ducking instead
+     * of the application losing focus and being signaled it can duck (as communicated by
+     * {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}).
+     * <br>Can only be used after having set a listener with
+     * {@link AudioPolicy#setAudioPolicyFocusListener(AudioPolicyFocusListener)}.
+     */
+    @SystemApi
+    public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1;
+
+    private AudioPolicyFocusListener mFocusListener;
+
     private Context mContext;
 
     private AudioPolicyConfig mConfig;
+
     /** @hide */
     public AudioPolicyConfig getConfig() { return mConfig; }
+    /** @hide */
+    public boolean hasFocusListener() { return mFocusListener != null; }
 
     /**
      * The parameter is guaranteed non-null through the Builder
      */
-    private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper) {
+    private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper,
+            AudioPolicyFocusListener fl, AudioPolicyStatusListener sl) {
         mConfig = config;
         mStatus = POLICY_STATUS_UNREGISTERED;
         mContext = context;
@@ -88,6 +114,8 @@
             mEventHandler = null;
             Log.e(TAG, "No event handler due to looper without a thread");
         }
+        mFocusListener = fl;
+        mStatusListener = sl;
     }
 
     /**
@@ -98,11 +126,14 @@
         private ArrayList<AudioMix> mMixes;
         private Context mContext;
         private Looper mLooper;
+        private AudioPolicyFocusListener mFocusListener;
+        private AudioPolicyStatusListener mStatusListener;
 
         /**
          * Constructs a new Builder with no audio mixes.
          * @param context the context for the policy
          */
+        @SystemApi
         public Builder(Context context) {
             mMixes = new ArrayList<AudioMix>();
             mContext = context;
@@ -114,6 +145,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
+        @SystemApi
         public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
             if (mix == null) {
                 throw new IllegalArgumentException("Illegal null AudioMix argument");
@@ -128,6 +160,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
+        @SystemApi
         public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
             if (looper == null) {
                 throw new IllegalArgumentException("Illegal null Looper argument");
@@ -136,34 +169,58 @@
             return this;
         }
 
+        /**
+         * Sets the audio focus listener for the policy.
+         * @param l a {@link AudioPolicy.AudioPolicyFocusListener}
+         */
+        @SystemApi
+        public void setAudioPolicyFocusListener(AudioPolicyFocusListener l) {
+            mFocusListener = l;
+        }
+
+        /**
+         * Sets the audio policy status listener.
+         * @param l a {@link AudioPolicy.AudioPolicyStatusListener}
+         */
+        @SystemApi
+        public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
+            mStatusListener = l;
+        }
+
+        @SystemApi
         public AudioPolicy build() {
-            return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper);
+            return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper,
+                    mFocusListener, mStatusListener);
         }
     }
 
     public void setRegistration(String regId) {
-        mRegistrationId = regId;
-        mConfig.setRegistration(regId);
-        if (regId != null) {
-            mStatus = POLICY_STATUS_REGISTERED;
-        } else {
-            mStatus = POLICY_STATUS_UNREGISTERED;
+        synchronized (mLock) {
+            mRegistrationId = regId;
+            mConfig.setRegistration(regId);
+            if (regId != null) {
+                mStatus = POLICY_STATUS_REGISTERED;
+            } else {
+                mStatus = POLICY_STATUS_UNREGISTERED;
+            }
         }
-        sendMsg(mEventHandler, MSG_POLICY_STATUS_CHANGE);
+        sendMsg(MSG_POLICY_STATUS_CHANGE);
     }
 
     private boolean policyReadyToUse() {
-        if (mStatus != POLICY_STATUS_REGISTERED) {
-            Log.e(TAG, "Cannot use unregistered AudioPolicy");
-            return false;
-        }
-        if (mContext == null) {
-            Log.e(TAG, "Cannot use AudioPolicy without context");
-            return false;
-        }
-        if (mRegistrationId == null) {
-            Log.e(TAG, "Cannot use unregistered AudioPolicy");
-            return false;
+        synchronized (mLock) {
+            if (mStatus != POLICY_STATUS_REGISTERED) {
+                Log.e(TAG, "Cannot use unregistered AudioPolicy");
+                return false;
+            }
+            if (mContext == null) {
+                Log.e(TAG, "Cannot use AudioPolicy without context");
+                return false;
+            }
+            if (mRegistrationId == null) {
+                Log.e(TAG, "Cannot use unregistered AudioPolicy");
+                return false;
+            }
         }
         if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
                         android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
@@ -199,6 +256,60 @@
     }
 
     /**
+     * Returns the current behavior for audio focus-related ducking.
+     * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
+     */
+    @SystemApi
+    public int getFocusDuckingBehavior() {
+        return mConfig.mDuckingPolicy;
+    }
+
+    // Note on implementation: not part of the Builder as there can be only one registered policy
+    // that handles ducking but there can be multiple policies
+    /**
+     * Sets the behavior for audio focus-related ducking.
+     * There must be a focus listener if this policy is to handle ducking.
+     * @param behavior {@link #FOCUS_POLICY_DUCKING_IN_APP} or
+     *     {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
+     * @return {@link AudioManager#SUCCESS} or {@link AudioManager#ERROR} (for instance if there
+     *     is already an audio policy that handles ducking).
+     * @throws IllegalArgumentException
+     * @throws IllegalStateException
+     */
+    @SystemApi
+    public int setFocusDuckingBehavior(int behavior)
+            throws IllegalArgumentException, IllegalStateException {
+        if ((behavior != FOCUS_POLICY_DUCKING_IN_APP)
+                && (behavior != FOCUS_POLICY_DUCKING_IN_POLICY)) {
+            throw new IllegalArgumentException("Invalid ducking behavior " + behavior);
+        }
+        synchronized (mLock) {
+            if (mStatus != POLICY_STATUS_REGISTERED) {
+                throw new IllegalStateException(
+                        "Cannot change ducking behavior for unregistered policy");
+            }
+            if ((behavior == FOCUS_POLICY_DUCKING_IN_POLICY)
+                    && (mFocusListener == null)) {
+                // there must be a focus listener if the policy handles ducking
+                throw new IllegalStateException(
+                        "Cannot handle ducking without an audio focus listener");
+            }
+            IAudioService service = getService();
+            try {
+                final int status = service.setFocusPropertiesForPolicy(behavior /*duckingBehavior*/,
+                        this.cb());
+                if (status == AudioManager.SUCCESS) {
+                    mConfig.mDuckingPolicy = behavior;
+                }
+                return status;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in setFocusPropertiesForPolicy for behavior", e);
+                return AudioManager.ERROR;
+            }
+        }
+    }
+
+    /**
      * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
      * Audio buffers recorded through the created instance will contain the mix of the audio
      * streams that fed the given mixer.
@@ -282,21 +393,53 @@
     }
 
     @SystemApi
-    synchronized public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
-        mStatusListener = l;
+    public static abstract class AudioPolicyFocusListener {
+        public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {}
+        public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {}
     }
 
-    synchronized private void onPolicyStatusChange() {
-        if (mStatusListener == null) {
-            return;
+    private void onPolicyStatusChange() {
+        AudioPolicyStatusListener l;
+        synchronized (mLock) {
+            if (mStatusListener == null) {
+                return;
+            }
+            l = mStatusListener;
         }
-        mStatusListener.onStatusChange();
+        l.onStatusChange();
     }
 
     //==================================================
+    // Callback interface
+
+    /** @hide */
+    public IAudioPolicyCallback cb() { return mPolicyCb; }
+
+    private final IAudioPolicyCallback mPolicyCb = new IAudioPolicyCallback.Stub() {
+
+        public void notifyAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
+            sendMsg(MSG_FOCUS_GRANT, afi, requestResult);
+            if (DEBUG) {
+                Log.v(TAG, "notifyAudioFocusGrant: pack=" + afi.getPackageName() + " client="
+                        + afi.getClientId() + "reqRes=" + requestResult);
+            }
+        }
+
+        public void notifyAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
+            sendMsg(MSG_FOCUS_LOSS, afi, wasNotified ? 1 : 0);
+            if (DEBUG) {
+                Log.v(TAG, "notifyAudioFocusLoss: pack=" + afi.getPackageName() + " client="
+                        + afi.getClientId() + "wasNotified=" + wasNotified);
+            }
+        }
+    };
+
+    //==================================================
     // Event handling
     private final EventHandler mEventHandler;
     private final static int MSG_POLICY_STATUS_CHANGE = 0;
+    private final static int MSG_FOCUS_GRANT = 1;
+    private final static int MSG_FOCUS_LOSS = 2;
 
     private class EventHandler extends Handler {
         public EventHandler(AudioPolicy ap, Looper looper) {
@@ -309,6 +452,18 @@
                 case MSG_POLICY_STATUS_CHANGE:
                     onPolicyStatusChange();
                     break;
+                case MSG_FOCUS_GRANT:
+                    if (mFocusListener != null) {
+                        mFocusListener.onAudioFocusGrant(
+                                (AudioFocusInfo) msg.obj, msg.arg1);
+                    }
+                    break;
+                case MSG_FOCUS_LOSS:
+                    if (mFocusListener != null) {
+                        mFocusListener.onAudioFocusLoss(
+                                (AudioFocusInfo) msg.obj, msg.arg1 != 0);
+                    }
+                    break;
                 default:
                     Log.e(TAG, "Unknown event " + msg.what);
             }
@@ -321,12 +476,31 @@
         return "addr=" + mix.getRegistration();
     }
 
-    private static void sendMsg(Handler handler, int msg) {
-        if (handler != null) {
-            handler.sendEmptyMessage(msg);
+    private void sendMsg(int msg) {
+        if (mEventHandler != null) {
+            mEventHandler.sendEmptyMessage(msg);
         }
     }
 
+    private void sendMsg(int msg, Object obj, int i) {
+        if (mEventHandler != null) {
+            mEventHandler.sendMessage(
+                    mEventHandler.obtainMessage(msg, i /*arg1*/, 0 /*arg2, ignored*/, obj));
+        }
+    }
+
+    private static IAudioService sService;
+
+    private static IAudioService getService()
+    {
+        if (sService != null) {
+            return sService;
+        }
+        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+        sService = IAudioService.Stub.asInterface(b);
+        return sService;
+    }
+
     public String toLogFriendlyString() {
         String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
         textDump += "config=" + mConfig.toLogFriendlyString();
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index e2a20da..019309d 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -38,6 +38,7 @@
     private static final String TAG = "AudioPolicyConfig";
 
     protected ArrayList<AudioMix> mMixes;
+    protected int mDuckingPolicy = AudioPolicy.FOCUS_POLICY_DUCKING_IN_APP;
 
     private String mRegistrationId = null;
 
diff --git a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
new file mode 100644
index 0000000..c777c58
--- /dev/null
+++ b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
@@ -0,0 +1,28 @@
+/* Copyright (C) 2014 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.audiopolicy;
+
+import android.media.AudioFocusInfo;
+
+/**
+ * @hide
+ */
+oneway interface IAudioPolicyCallback {
+
+    // callbacks for audio focus
+    void notifyAudioFocusGrant(in AudioFocusInfo afi, int requestResult);
+    void notifyAudioFocusLoss(in AudioFocusInfo afi, boolean wasNotified);
+}