Merge "Fixed NPE during monkey testing."
diff --git a/api/current.txt b/api/current.txt
index ba9b1cc..d030fa9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4171,6 +4171,7 @@
     method public abstract void onActivityCreated(android.app.Activity, android.os.Bundle);
     method public abstract void onActivityDestroyed(android.app.Activity);
     method public abstract void onActivityPaused(android.app.Activity);
+    method public default void onActivityPreCreated(android.app.Activity, android.os.Bundle);
     method public abstract void onActivityResumed(android.app.Activity);
     method public abstract void onActivitySaveInstanceState(android.app.Activity, android.os.Bundle);
     method public abstract void onActivityStarted(android.app.Activity);
diff --git a/api/system-current.txt b/api/system-current.txt
index a0b9500..b834594 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4313,6 +4313,7 @@
     method public abstract void onActivityCreated(android.app.Activity, android.os.Bundle);
     method public abstract void onActivityDestroyed(android.app.Activity);
     method public abstract void onActivityPaused(android.app.Activity);
+    method public default void onActivityPreCreated(android.app.Activity, android.os.Bundle);
     method public abstract void onActivityResumed(android.app.Activity);
     method public abstract void onActivitySaveInstanceState(android.app.Activity, android.os.Bundle);
     method public abstract void onActivityStarted(android.app.Activity);
@@ -22084,6 +22085,7 @@
     method public int describeContents();
     method public android.media.AudioAttributes getAttributes();
     method public java.lang.String getClientId();
+    method public int getClientUid();
     method public int getFlags();
     method public int getGainRequest();
     method public int getLossReceived();
@@ -24268,10 +24270,12 @@
   }
 
   public class PlayerProxy {
-    method public void pause() throws java.lang.IllegalStateException;
-    method public void setVolume(float) throws java.lang.IllegalStateException;
-    method public void start() throws java.lang.IllegalStateException;
-    method public void stop() throws java.lang.IllegalStateException;
+    method public void pause();
+    method public void setPan(float);
+    method public void setStartDelayMs(int);
+    method public void setVolume(float);
+    method public void start();
+    method public void stop();
   }
 
   public final class Rating implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 682c029..ff7e2c2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4181,6 +4181,7 @@
     method public abstract void onActivityCreated(android.app.Activity, android.os.Bundle);
     method public abstract void onActivityDestroyed(android.app.Activity);
     method public abstract void onActivityPaused(android.app.Activity);
+    method public default void onActivityPreCreated(android.app.Activity, android.os.Bundle);
     method public abstract void onActivityResumed(android.app.Activity);
     method public abstract void onActivitySaveInstanceState(android.app.Activity, android.os.Bundle);
     method public abstract void onActivityStarted(android.app.Activity);
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 9fad7bf..c773275 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -107,8 +107,6 @@
     mSystemBoot = !property_get_bool(BOOT_COMPLETED_PROP_NAME, 0);
 }
 
-BootAnimation::~BootAnimation() {}
-
 void BootAnimation::onFirstRef() {
     status_t err = mSession->linkToComposerDeath(this);
     ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
@@ -778,11 +776,12 @@
     }
 
     // Create and initialize audioplay if there is a wav file in any of the animations.
+    // Do it on a separate thread so we don't hold up the animation intro.
     if (partWithAudio != NULL) {
         ALOGD("found audio.wav, creating playback engine");
-        if (!audioplay::create(partWithAudio->audioData, partWithAudio->audioLength)) {
-            return false;
-        }
+        mInitAudioThread = new InitAudioThread(partWithAudio->audioData,
+                                               partWithAudio->audioLength);
+        mInitAudioThread->run("BootAnimation::InitAudioThread", PRIORITY_NORMAL);
     }
 
     zip->endIteration(cookie);
@@ -850,9 +849,14 @@
 
     playAnimation(*animation);
 
-    if (mTimeCheckThread != NULL) {
+    if (mTimeCheckThread != nullptr) {
         mTimeCheckThread->requestExit();
-        mTimeCheckThread = NULL;
+        mTimeCheckThread = nullptr;
+    }
+
+    if (mInitAudioThread != nullptr) {
+        mInitAudioThread->requestExit();
+        mInitAudioThread = nullptr;
     }
 
     releaseAnimation(animation);
@@ -892,6 +896,10 @@
             // only play audio file the first time we animate the part
             if (r == 0 && part.audioData && playSoundsAllowed()) {
                 ALOGD("playing clip for part%d, size=%d", (int) i, part.audioLength);
+                // Block until the audio engine is finished initializing.
+                if (mInitAudioThread != nullptr) {
+                    mInitAudioThread->join();
+                }
                 audioplay::playClip(part.audioData, part.audioLength);
             }
 
@@ -1185,6 +1193,17 @@
     return NO_ERROR;
 }
 
+BootAnimation::InitAudioThread::InitAudioThread(uint8_t* exampleAudioData, int exampleAudioLength)
+    : Thread(false),
+      mExampleAudioData(exampleAudioData),
+      mExampleAudioLength(exampleAudioLength) {}
+
+bool BootAnimation::InitAudioThread::threadLoop() {
+    audioplay::create(mExampleAudioData, mExampleAudioLength);
+    // Exit immediately
+    return false;
+}
+
 // ---------------------------------------------------------------------------
 
 }
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index 7a2e4c2..f1fc98e 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -39,8 +39,7 @@
 class BootAnimation : public Thread, public IBinder::DeathRecipient
 {
 public:
-                BootAnimation();
-    virtual     ~BootAnimation();
+    BootAnimation();
 
     sp<SurfaceComposerClient> session() const;
 
@@ -68,6 +67,16 @@
         BootAnimation* mBootAnimation;
     };
 
+    class InitAudioThread : public Thread {
+    public:
+        InitAudioThread(uint8_t* exampleAudioData, int mExampleAudioLength);
+    private:
+        virtual bool threadLoop();
+
+        uint8_t* mExampleAudioData;
+        int mExampleAudioLength;
+    };
+
     struct Texture {
         GLint   w;
         GLint   h;
@@ -156,7 +165,8 @@
     bool        mSystemBoot;
     String8     mZipFileName;
     SortedVector<String8> mLoadedFiles;
-    sp<TimeCheckThread> mTimeCheckThread;
+    sp<TimeCheckThread> mTimeCheckThread = nullptr;
+    sp<InitAudioThread> mInitAudioThread = nullptr;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 6f95309..0ac30de 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -969,7 +969,9 @@
                     ? mLastNonConfigurationInstances.fragments : null);
         }
         mFragments.dispatchCreate();
-        getApplication().dispatchActivityCreated(this, savedInstanceState);
+        if (!isAtLeastO()) {
+            getApplication().dispatchActivityCreated(this, savedInstanceState);
+        }
         if (mVoiceInteractor != null) {
             mVoiceInteractor.attachActivity(this);
         }
@@ -1197,8 +1199,9 @@
         mCalled = true;
 
         mFragments.doLoaderStart();
-
-        getApplication().dispatchActivityStarted(this);
+        if (!isAtLeastO()) {
+            getApplication().dispatchActivityStarted(this);
+        }
     }
 
     /**
@@ -1259,7 +1262,9 @@
     @CallSuper
     protected void onResume() {
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
-        getApplication().dispatchActivityResumed(this);
+        if (!isAtLeastO()) {
+            getApplication().dispatchActivityResumed(this);
+        }
         mActivityTransitionState.onResume(this, isTopOfTask());
         mCalled = true;
     }
@@ -1426,6 +1431,9 @@
         saveManagedDialogs(outState);
         mActivityTransitionState.saveState(outState);
         storeHasCurrentPermissionRequest(outState);
+        if (isAtLeastO()) {
+            getApplication().dispatchActivitySaveInstanceState(this, outState);
+        }
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
     }
 
@@ -1442,6 +1450,9 @@
         onSaveInstanceState(outState, outPersistentState);
         saveManagedDialogs(outState);
         storeHasCurrentPermissionRequest(outState);
+        if (isAtLeastO()) {
+            getApplication().dispatchActivitySaveInstanceState(this, outState);
+        }
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState +
                 ", " + outPersistentState);
     }
@@ -1497,7 +1508,9 @@
         if (p != null) {
             outState.putParcelable(FRAGMENTS_TAG, p);
         }
-        getApplication().dispatchActivitySaveInstanceState(this, outState);
+        if (!isAtLeastO()) {
+            getApplication().dispatchActivitySaveInstanceState(this, outState);
+        }
     }
 
     /**
@@ -1595,7 +1608,9 @@
     @CallSuper
     protected void onPause() {
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this);
-        getApplication().dispatchActivityPaused(this);
+        if (!isAtLeastO()) {
+            getApplication().dispatchActivityPaused(this);
+        }
         mCalled = true;
     }
 
@@ -1795,7 +1810,9 @@
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
         if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
         mActivityTransitionState.onStop();
-        getApplication().dispatchActivityStopped(this);
+        if (!isAtLeastO()) {
+            getApplication().dispatchActivityStopped(this);
+        }
         mTranslucentCallback = null;
         mCalled = true;
     }
@@ -1865,8 +1882,9 @@
         if (mActionBar != null) {
             mActionBar.onDestroy();
         }
-
-        getApplication().dispatchActivityDestroyed(this);
+        if (!isAtLeastO()) {
+            getApplication().dispatchActivityDestroyed(this);
+        }
     }
 
     /**
@@ -6750,25 +6768,33 @@
         return mParent != null ? mParent.getActivityToken() : mToken;
     }
 
-    final void performCreateCommon() {
+    final void performCreateCommon(Bundle icicle) {
+        mActivityTransitionState.readState(icicle);
         mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
                 com.android.internal.R.styleable.Window_windowNoDisplay, false);
         mFragments.dispatchActivityCreated();
         mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
+        if (isAtLeastO()) {
+            getApplication().dispatchActivityCreated(this, icicle);
+        }
     }
 
     final void performCreate(Bundle icicle) {
         restoreHasCurrentPermissionRequest(icicle);
+        if (isAtLeastO()) {
+            getApplication().dispatchActivityPreCreated(this, icicle);
+        }
         onCreate(icicle);
-        mActivityTransitionState.readState(icicle);
-        performCreateCommon();
+        performCreateCommon(icicle);
     }
 
     final void performCreate(Bundle icicle, PersistableBundle persistentState) {
         restoreHasCurrentPermissionRequest(icicle);
+        if (isAtLeastO()) {
+            getApplication().dispatchActivityPreCreated(this, icicle);
+        }
         onCreate(icicle, persistentState);
-        mActivityTransitionState.readState(icicle);
-        performCreateCommon();
+        performCreateCommon(icicle);
     }
 
     final void performStart() {
@@ -6811,6 +6837,9 @@
         }
 
         mActivityTransitionState.enterReady(this);
+        if (isAtLeastO()) {
+            getApplication().dispatchActivityStarted(this);
+        }
     }
 
     final void performRestart() {
@@ -6886,7 +6915,9 @@
 
         mFragments.dispatchResume();
         mFragments.execPendingActions();
-
+        if (isAtLeastO()) {
+            getApplication().dispatchActivityResumed(this);
+        }
         onPostResume();
         if (!mCalled) {
             throw new SuperNotCalledException(
@@ -6901,13 +6932,15 @@
         mCalled = false;
         onPause();
         mResumed = false;
+        if (isAtLeastO()) {
+            getApplication().dispatchActivityPaused(this);
+        }
         if (!mCalled && getApplicationInfo().targetSdkVersion
                 >= android.os.Build.VERSION_CODES.GINGERBREAD) {
             throw new SuperNotCalledException(
                     "Activity " + mComponent.toShortString() +
                     " did not call through to super.onPause()");
         }
-        mResumed = false;
     }
 
     final void performUserLeaving() {
@@ -6918,7 +6951,7 @@
     final void performStop(boolean preserveWindow) {
         mDoReportFullyDrawn = false;
         mFragments.doLoaderStop(mChangingConfigurations /*retain*/);
-
+        boolean dispatchActivityStopped = !mStopped;
         if (!mStopped) {
             if (mWindow != null) {
                 mWindow.closeAllPanels();
@@ -6955,6 +6988,9 @@
             mStopped = true;
         }
         mResumed = false;
+        if (dispatchActivityStopped && isAtLeastO()) {
+            getApplication().dispatchActivityStopped(this);
+        }
     }
 
     final void performDestroy() {
@@ -6966,6 +7002,13 @@
         if (mVoiceInteractor != null) {
             mVoiceInteractor.detachActivity();
         }
+        if (isAtLeastO()) {
+            getApplication().dispatchActivityDestroyed(this);
+        }
+    }
+
+    private boolean isAtLeastO() {
+        return getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O;
     }
 
     final void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 156df36..03efe68 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -55,6 +55,7 @@
     public LoadedApk mLoadedApk;
 
     public interface ActivityLifecycleCallbacks {
+        default void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {}
         void onActivityCreated(Activity activity, Bundle savedInstanceState);
         void onActivityStarted(Activity activity);
         void onActivityResumed(Activity activity);
@@ -190,6 +191,16 @@
         mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
     }
 
+    /* package */ void dispatchActivityPreCreated(Activity activity, Bundle savedInstanceState) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPreCreated(activity,
+                        savedInstanceState);
+            }
+        }
+    }
+
     /* package */ void dispatchActivityCreated(Activity activity, Bundle savedInstanceState) {
         Object[] callbacks = collectActivityLifecycleCallbacks();
         if (callbacks != null) {
diff --git a/core/tests/coretests/src/android/provider/SettingsTest.java b/core/tests/coretests/src/android/provider/SettingsTest.java
index 3d21817..d76980a 100644
--- a/core/tests/coretests/src/android/provider/SettingsTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsTest.java
@@ -244,6 +244,7 @@
                     Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES,
                     Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE,
                     Settings.Global.NETWORK_AVOID_BAD_WIFI,
+                    Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
                     Settings.Global.NETWORK_PREFERENCE,
                     Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
                     Settings.Global.NETWORK_SCORER_APP,
@@ -331,6 +332,7 @@
                     Settings.Global.WFC_IMS_MODE,
                     Settings.Global.WFC_IMS_ROAMING_ENABLED,
                     Settings.Global.WFC_IMS_ROAMING_MODE,
+                    Settings.Global.WIFI_BADGING_THRESHOLDS,
                     Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS,
                     Settings.Global.WIFI_COUNTRY_CODE,
                     Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
diff --git a/data/keyboards/qwerty.kl b/data/keyboards/qwerty.kl
index 4186007..2fd99ab 100644
--- a/data/keyboards/qwerty.kl
+++ b/data/keyboards/qwerty.kl
@@ -123,3 +123,9 @@
 key 166   MEDIA_STOP
 key 167   MEDIA_RECORD
 key 168   MEDIA_REWIND
+
+key 142   SLEEP
+key 581   STEM_PRIMARY
+key 582   STEM_1
+key 583   STEM_2
+key 584   STEM_3
diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java
index 540c328..60dbe00 100644
--- a/media/java/android/media/AudioFocusInfo.java
+++ b/media/java/android/media/AudioFocusInfo.java
@@ -29,9 +29,10 @@
 @SystemApi
 public final class AudioFocusInfo implements Parcelable {
 
-    private AudioAttributes mAttributes;
-    private String mClientId;
-    private String mPackageName;
+    private final AudioAttributes mAttributes;
+    private final int mClientUid;
+    private final String mClientId;
+    private final String mPackageName;
     private int mGainRequest;
     private int mLossReceived;
     private int mFlags;
@@ -47,9 +48,10 @@
      * @param flags
      * @hide
      */
-    public AudioFocusInfo(AudioAttributes aa, String clientId, String packageName,
+    public AudioFocusInfo(AudioAttributes aa, int clientUid, String clientId, String packageName,
             int gainRequest, int lossReceived, int flags) {
         mAttributes = aa == null ? new AudioAttributes.Builder().build() : aa;
+        mClientUid = clientUid;
         mClientId = clientId == null ? "" : clientId;
         mPackageName = packageName == null ? "" : packageName;
         mGainRequest = gainRequest;
@@ -66,6 +68,9 @@
     public AudioAttributes getAttributes() { return mAttributes; }
 
     @SystemApi
+    public int getClientUid() { return mClientUid; }
+
+    @SystemApi
     public String getClientId() { return mClientId; }
 
     @SystemApi
@@ -111,6 +116,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         mAttributes.writeToParcel(dest, flags);
+        dest.writeInt(mClientUid);
         dest.writeString(mClientId);
         dest.writeString(mPackageName);
         dest.writeInt(mGainRequest);
@@ -121,7 +127,7 @@
     @SystemApi
     @Override
     public int hashCode() {
-        return Objects.hash(mAttributes, mClientId, mPackageName, mGainRequest, mFlags);
+        return Objects.hash(mAttributes, mClientUid, mClientId, mPackageName, mGainRequest, mFlags);
     }
 
     @SystemApi
@@ -137,6 +143,9 @@
         if (!mAttributes.equals(other.mAttributes)) {
             return false;
         }
+        if (mClientUid != other.mClientUid) {
+            return false;
+        }
         if (!mClientId.equals(other.mClientId)) {
             return false;
         }
@@ -161,6 +170,7 @@
         public AudioFocusInfo createFromParcel(Parcel in) {
             return new AudioFocusInfo(
                     AudioAttributes.CREATOR.createFromParcel(in), //AudioAttributes aa
+                    in.readInt(), // int clientUid
                     in.readString(), //String clientId
                     in.readString(), //String packageName
                     in.readInt(), //int gainRequest
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index fa904cd..fb3f5b3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2430,6 +2430,23 @@
 
     /**
      * @hide
+     * Return the volume ramping time for a sound to be played after the given focus request,
+     *   and to play a sound of the given attributes
+     * @param focusGain
+     * @param attr
+     * @return
+     */
+    public int getFocusRampTimeMs(int focusGain, AudioAttributes attr) {
+        IAudioService service = getService();
+        try {
+            return service.getFocusRampTimeMs(focusGain, attr);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
      * Used internally by telephony package to abandon audio focus, typically after a call or
      * when ringing ends and the call is rejected or not answered.
      * Should match one or more calls to {@link #requestAudioFocusForCall(int, int)}.
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index b23f5fd..5c9f270 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1880,6 +1880,26 @@
         if (mState != STATE_INITIALIZED) {
             throw new IllegalStateException("play() called on uninitialized AudioTrack.");
         }
+        //FIXME use lambda to pass startImpl to superclass
+        final int delay = getStartDelayMs();
+        if (delay == 0) {
+            startImpl();
+        } else {
+            new Thread() {
+                public void run() {
+                    try {
+                        Thread.sleep(delay);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                    baseSetStartDelayMs(0);
+                    startImpl();
+                }
+            }.start();
+        }
+    }
+
+    private void startImpl() {
         synchronized(mPlayStateLock) {
             baseStart();
             native_start();
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index a76a328..fa4796a 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -194,5 +194,7 @@
 
     void disableRingtoneSync();
 
+    int getFocusRampTimeMs(in int focusGain, in AudioAttributes attr);
+
     // WARNING: read warning at top of file, it is recommended to add new methods at the end
 }
diff --git a/media/java/android/media/IPlayer.aidl b/media/java/android/media/IPlayer.aidl
index ccb60f7..f068a0a 100644
--- a/media/java/android/media/IPlayer.aidl
+++ b/media/java/android/media/IPlayer.aidl
@@ -25,4 +25,6 @@
     oneway void pause();
     oneway void stop();
     oneway void setVolume(float vol);
+    oneway void setPan(float pan);
+    oneway void setStartDelayMs(int delayMs);
 }
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 62abdefd..03dc2ea 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1245,6 +1245,26 @@
      * @throws IllegalStateException if it is called in an invalid state
      */
     public void start() throws IllegalStateException {
+        //FIXME use lambda to pass startImpl to superclass
+        final int delay = getStartDelayMs();
+        if (delay == 0) {
+            startImpl();
+        } else {
+            new Thread() {
+                public void run() {
+                    try {
+                        Thread.sleep(delay);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                    baseSetStartDelayMs(0);
+                    startImpl();
+                }
+            }.start();
+        }
+    }
+
+    private void startImpl() {
         baseStart();
         stayAwake(true);
         _start();
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index bd0a1b4..d2b052a 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -58,14 +58,17 @@
     // for AppOps
     private IAppOpsService mAppOps;
     private IAppOpsCallback mAppOpsCallback;
-    private boolean mHasAppOpsPlayAudio = true;
-    private final Object mAppOpsLock = new Object();
+    private boolean mHasAppOpsPlayAudio = true; // sync'd on mLock
+    private final Object mLock = new Object();
 
     private final int mImplType;
     // uniquely identifies the Player Interface throughout the system (P I Id)
     private int mPlayerIId;
 
-    private int mState;
+    private int mState; // sync'd on mLock
+    private int mStartDelayMs = 0; // sync'd on mLock
+    private float mPanMultiplierL = 1.0f; // sync'd on mLock
+    private float mPanMultiplierR = 1.0f; // sync'd on mLock
 
     /**
      * Constructor. Must be given audio attributes, as they are required for AppOps.
@@ -89,11 +92,13 @@
         IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
         mAppOps = IAppOpsService.Stub.asInterface(b);
         // initialize mHasAppOpsPlayAudio
-        updateAppOpsPlayAudio_sync();
+        synchronized (mLock) {
+            updateAppOpsPlayAudio_sync();
+        }
         // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
         mAppOpsCallback = new IAppOpsCallback.Stub() {
             public void opChanged(int op, int uid, String packageName) {
-                synchronized (mAppOpsLock) {
+                synchronized (mLock) {
                     if (op == AppOpsManager.OP_PLAY_AUDIO) {
                         updateAppOpsPlayAudio_sync();
                     }
@@ -130,7 +135,7 @@
         } catch (RemoteException e) {
             Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e);
         }
-        synchronized (mAppOpsLock) {
+        synchronized (mLock) {
             mAttributes = attr;
             updateAppOpsPlayAudio_sync();
         }
@@ -139,23 +144,39 @@
     void baseStart() {
         if (DEBUG) { Log.v(TAG, "baseStart() piid=" + mPlayerIId); }
         try {
-            mState = AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
-            getService().playerEvent(mPlayerIId, mState);
+            synchronized (mLock) {
+                mState = AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
+                getService().playerEvent(mPlayerIId, mState);
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e);
         }
-        synchronized (mAppOpsLock) {
+        synchronized (mLock) {
             if (isRestricted_sync()) {
                 playerSetVolume(true/*muting*/,0, 0);
             }
         }
     }
 
+    void baseSetStartDelayMs(int delayMs) {
+        synchronized(mLock) {
+            mStartDelayMs = Math.max(delayMs, 0);
+        }
+    }
+
+    protected int getStartDelayMs() {
+        synchronized(mLock) {
+            return mStartDelayMs;
+        }
+    }
+
     void basePause() {
         if (DEBUG) { Log.v(TAG, "basePause() piid=" + mPlayerIId); }
         try {
-            mState = AudioPlaybackConfiguration.PLAYER_STATE_PAUSED;
-            getService().playerEvent(mPlayerIId, mState);
+            synchronized (mLock) {
+                mState = AudioPlaybackConfiguration.PLAYER_STATE_PAUSED;
+                getService().playerEvent(mPlayerIId, mState);
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "Error talking to audio service, PAUSED state will not be tracked", e);
         }
@@ -164,26 +185,45 @@
     void baseStop() {
         if (DEBUG) { Log.v(TAG, "baseStop() piid=" + mPlayerIId); }
         try {
-            mState = AudioPlaybackConfiguration.PLAYER_STATE_STOPPED;
-            getService().playerEvent(mPlayerIId, mState);
+            synchronized (mLock) {
+                mState = AudioPlaybackConfiguration.PLAYER_STATE_STOPPED;
+                getService().playerEvent(mPlayerIId, mState);
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "Error talking to audio service, STOPPED state will not be tracked", e);
         }
     }
 
+    void baseSetPan(float pan) {
+        final float p = Math.min(Math.max(-1.0f, pan), 1.0f);
+        synchronized (mLock) {
+            if (p >= 0.0f) {
+                mPanMultiplierL = 1.0f - p;
+                mPanMultiplierR = 1.0f;
+            } else {
+                mPanMultiplierL = 1.0f;
+                mPanMultiplierR = 1.0f + p;
+            }
+        }
+        baseSetVolume(mLeftVolume, mRightVolume);
+    }
+
     void baseSetVolume(float leftVolume, float rightVolume) {
-        synchronized (mAppOpsLock) {
+        final boolean hasAppOpsPlayAudio;
+        synchronized (mLock) {
             mLeftVolume = leftVolume;
             mRightVolume = rightVolume;
+            hasAppOpsPlayAudio = mHasAppOpsPlayAudio;
             if (isRestricted_sync()) {
                 return;
             }
         }
-        playerSetVolume(false/*muting*/,leftVolume, rightVolume);
+        playerSetVolume(!hasAppOpsPlayAudio/*muting*/,
+                leftVolume * mPanMultiplierL, rightVolume * mPanMultiplierR);
     }
 
     int baseSetAuxEffectSendLevel(float level) {
-        synchronized (mAppOpsLock) {
+        synchronized (mLock) {
             mAuxEffectSendLevel = level;
             if (isRestricted_sync()) {
                 return AudioSystem.SUCCESS;
@@ -199,9 +239,11 @@
     void baseRelease() {
         if (DEBUG) { Log.v(TAG, "baseRelease() piid=" + mPlayerIId + " state=" + mState); }
         try {
-            if (mState != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
-                getService().releasePlayer(mPlayerIId);
-                mState = AudioPlaybackConfiguration.PLAYER_STATE_RELEASED;
+            synchronized (mLock) {
+                if (mState != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
+                    getService().releasePlayer(mPlayerIId);
+                    mState = AudioPlaybackConfiguration.PLAYER_STATE_RELEASED;
+                }
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error talking to audio service, the player will still be tracked", e);
@@ -215,7 +257,7 @@
 
     /**
      * To be called whenever a condition that might affect audibility of this player is updated.
-     * Must be called synchronized on mAppOpsLock.
+     * Must be called synchronized on mLock.
      */
     void updateAppOpsPlayAudio_sync() {
         boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio;
@@ -237,7 +279,8 @@
                         Log.v(TAG, "updateAppOpsPlayAudio: unmuting player, vol=" + mLeftVolume
                                 + "/" + mRightVolume);
                     }
-                    playerSetVolume(false/*muting*/, mLeftVolume, mRightVolume);
+                    playerSetVolume(false/*muting*/,
+                            mLeftVolume * mPanMultiplierL, mRightVolume * mPanMultiplierR);
                     playerSetAuxEffectSendLevel(false/*muting*/, mAuxEffectSendLevel);
                 } else {
                     if (DEBUG_APP_OPS) {
@@ -297,6 +340,14 @@
         return sService;
     }
 
+    /**
+     * @hide
+     * @param delayMs
+     */
+    public void setStartDelayMs(int delayMs) {
+        baseSetStartDelayMs(delayMs);
+    }
+
     //=====================================================================
     // Abstract methods a subclass needs to implement
     /**
@@ -335,6 +386,16 @@
         public void setVolume(float vol) {
             baseSetVolume(vol, vol);
         }
+
+        @Override
+        public void setPan(float pan) {
+            baseSetPan(pan);
+        }
+
+        @Override
+        public void setStartDelayMs(int delayMs) {
+            baseSetStartDelayMs(delayMs);
+        }
     };
 
     //=====================================================================
diff --git a/media/java/android/media/PlayerProxy.java b/media/java/android/media/PlayerProxy.java
index 171be27..1a2c668 100644
--- a/media/java/android/media/PlayerProxy.java
+++ b/media/java/android/media/PlayerProxy.java
@@ -52,10 +52,9 @@
     // Methods matching the IPlayer interface
     /**
      * @hide
-     * @throws IllegalStateException
      */
     @SystemApi
-    public void start() throws IllegalStateException {
+    public void start() {
         try {
             mConf.getIPlayer().start();
         } catch (NullPointerException|RemoteException e) {
@@ -66,10 +65,9 @@
 
     /**
      * @hide
-     * @throws IllegalStateException
      */
     @SystemApi
-    public void pause() throws IllegalStateException {
+    public void pause() {
         try {
             mConf.getIPlayer().pause();
         } catch (NullPointerException|RemoteException e) {
@@ -80,10 +78,9 @@
 
     /**
      * @hide
-     * @throws IllegalStateException
      */
     @SystemApi
-    public void stop() throws IllegalStateException {
+    public void stop() {
         try {
             mConf.getIPlayer().stop();
         } catch (NullPointerException|RemoteException e) {
@@ -94,10 +91,10 @@
 
     /**
      * @hide
-     * @throws IllegalStateException
+     * @param vol
      */
     @SystemApi
-    public void setVolume(float vol) throws IllegalStateException {
+    public void setVolume(float vol) {
         try {
             mConf.getIPlayer().setVolume(vol);
         } catch (NullPointerException|RemoteException e) {
@@ -106,4 +103,33 @@
         }
     }
 
+    /**
+     * @hide
+     * @param pan
+     */
+    @SystemApi
+    public void setPan(float pan) {
+        try {
+            mConf.getIPlayer().setPan(pan);
+        } catch (NullPointerException|RemoteException e) {
+            throw new IllegalStateException(
+                    "No player to proxy for setPan operation, player already released?", e);
+        }
+    }
+
+    /**
+     * @hide
+     * @param delayMs
+     */
+    @SystemApi
+    public void setStartDelayMs(int delayMs) {
+        try {
+            mConf.getIPlayer().setStartDelayMs(delayMs);
+        } catch (NullPointerException|RemoteException e) {
+            throw new IllegalStateException(
+                    "No player to proxy for setStartDelayMs operation, player already released?",
+                    e);
+        }
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index 9788903..5cd7e41 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -41,7 +41,7 @@
 public class NotificationPlayer implements OnCompletionListener, OnErrorListener {
     private static final int PLAY = 1;
     private static final int STOP = 2;
-    private static final boolean mDebug = false;
+    private static final boolean DEBUG = false;
 
     private static final class Command {
         int code;
@@ -97,17 +97,18 @@
                         if (!audioManager.isMusicActiveRemotely()) {
                             synchronized(mQueueAudioFocusLock) {
                                 if (mAudioManagerWithAudioFocus == null) {
-                                    if (mDebug) Log.d(mTag, "requesting AudioFocus");
+                                    if (DEBUG) Log.d(mTag, "requesting AudioFocus");
+                                    int focusGain = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
                                     if (mCmd.looping) {
-                                        audioManager.requestAudioFocus(null, mCmd.attributes,
-                                                AudioManager.AUDIOFOCUS_GAIN, 0);
-                                    } else {
-                                        audioManager.requestAudioFocus(null, mCmd.attributes,
-                                                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
+                                        focusGain = AudioManager.AUDIOFOCUS_GAIN;
                                     }
+                                    mNotificationRampTimeMs = audioManager.getFocusRampTimeMs(
+                                            focusGain, mCmd.attributes);
+                                    audioManager.requestAudioFocus(null, mCmd.attributes,
+                                                focusGain, 0);
                                     mAudioManagerWithAudioFocus = audioManager;
                                 } else {
-                                    if (mDebug) Log.d(mTag, "AudioFocus was previously requested");
+                                    if (DEBUG) Log.d(mTag, "AudioFocus was previously requested");
                                 }
                             }
                         }
@@ -119,6 +120,9 @@
                     //  command are issued, and on which it receives the completion callbacks.
                     player.setOnCompletionListener(NotificationPlayer.this);
                     player.setOnErrorListener(NotificationPlayer.this);
+                    if (DEBUG)  { Log.d(mTag, "notification will be delayed by "
+                            + mNotificationRampTimeMs + "ms"); }
+                    player.setStartDelayMs(mNotificationRampTimeMs);
                     player.start();
                     if (mPlayer != null) {
                         mPlayer.release();
@@ -139,7 +143,7 @@
         // is playing, let it continue until we're done, so there
         // is less of a glitch.
         try {
-            if (mDebug) Log.d(mTag, "Starting playback");
+            if (DEBUG) Log.d(mTag, "Starting playback");
             //-----------------------------------
             // This is were we deviate from the AsyncPlayer implementation and create the
             // MediaPlayer in a new thread with which we're synchronized
@@ -179,17 +183,17 @@
                 Command cmd = null;
 
                 synchronized (mCmdQueue) {
-                    if (mDebug) Log.d(mTag, "RemoveFirst");
+                    if (DEBUG) Log.d(mTag, "RemoveFirst");
                     cmd = mCmdQueue.removeFirst();
                 }
 
                 switch (cmd.code) {
                 case PLAY:
-                    if (mDebug) Log.d(mTag, "PLAY");
+                    if (DEBUG) Log.d(mTag, "PLAY");
                     startSound(cmd);
                     break;
                 case STOP:
-                    if (mDebug) Log.d(mTag, "STOP");
+                    if (DEBUG) Log.d(mTag, "STOP");
                     if (mPlayer != null) {
                         long delay = SystemClock.uptimeMillis() - cmd.requestTime;
                         if (delay > 1000) {
@@ -232,11 +236,11 @@
     public void onCompletion(MediaPlayer mp) {
         synchronized(mQueueAudioFocusLock) {
             if (mAudioManagerWithAudioFocus != null) {
-                if (mDebug) Log.d(mTag, "onCompletion() abandonning AudioFocus");
+                if (DEBUG) Log.d(mTag, "onCompletion() abandonning AudioFocus");
                 mAudioManagerWithAudioFocus.abandonAudioFocus(null);
                 mAudioManagerWithAudioFocus = null;
             } else {
-                if (mDebug) Log.d(mTag, "onCompletion() no need to abandon AudioFocus");
+                if (DEBUG) Log.d(mTag, "onCompletion() no need to abandon AudioFocus");
             }
         }
         // if there are no more sounds to play, end the Looper to listen for media completion
@@ -267,6 +271,7 @@
     private PowerManager.WakeLock mWakeLock;
     private final Object mQueueAudioFocusLock = new Object();
     private AudioManager mAudioManagerWithAudioFocus; // synchronized on mQueueAudioFocusLock
+    private int mNotificationRampTimeMs = 0;
 
     // The current state according to the caller.  Reality lags behind
     // because of the asynchronous nature of this class.
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 0292db9..8020f0e 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -1660,15 +1660,15 @@
 
     @Override
     public void updateStatusIcon(IBinder token, String packageName, int iconId) {
-        long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (mMethodMap) {
-                if (!calledWithValidToken(token)) {
-                    final int uid = Binder.getCallingUid();
-                    Slog.e(TAG, "Ignoring updateStatusIcon due to an invalid token. uid:" + uid
-                            + " token:" + token);
-                    return;
-                }
+        synchronized (mMethodMap) {
+            if (!calledWithValidToken(token)) {
+                final int uid = Binder.getCallingUid();
+                Slog.e(TAG, "Ignoring updateStatusIcon due to an invalid token. uid:" + uid
+                        + " token:" + token);
+                return;
+            }
+            final long ident = Binder.clearCallingIdentity();
+            try {
                 if (iconId == 0) {
                     if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
                     if (mStatusBar != null) {
@@ -1693,9 +1693,9 @@
                         mStatusBar.setIconVisibility(mSlotIme, true);
                     }
                 }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
             }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index b2b3e61..4411794 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -4093,18 +4093,6 @@
 
     void setDockedStackMinimized(boolean minimized) {
         mIsDockMinimized = minimized;
-        if (minimized) {
-            // Docked stack is not visible, no need to confirm credentials for its top activity.
-            return;
-        }
-        final ActivityStack dockedStack = getStack(StackId.DOCKED_STACK_ID);
-        if (dockedStack == null) {
-            return;
-        }
-        final ActivityRecord top = dockedStack.topRunningActivityLocked();
-        if (top != null && mService.mUserController.shouldConfirmCredentials(top.userId)) {
-            mService.mActivityStarter.showConfirmDeviceCredential(top.userId);
-        }
     }
 
     private final class ActivityStackSupervisorHandler extends Handler {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 96f732e..73ef88b 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -608,50 +608,6 @@
         }
     }
 
-    void showConfirmDeviceCredential(int userId) {
-        // First, retrieve the stack that we want to resume after credential is confirmed.
-        ActivityStack targetStack;
-        ActivityStack fullscreenStack =
-                mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID);
-        if (fullscreenStack != null &&
-                fullscreenStack.getStackVisibilityLocked(null) != ActivityStack.STACK_INVISIBLE) {
-            // Single window case and the case that the docked stack is shown with fullscreen stack.
-            targetStack = fullscreenStack;
-        } else {
-            // The case that the docked stack is shown with recent.
-            targetStack = mSupervisor.getStack(HOME_STACK_ID);
-        }
-        if (targetStack == null) {
-            return;
-        }
-        final KeyguardManager km = (KeyguardManager) mService.mContext
-                .getSystemService(Context.KEYGUARD_SERVICE);
-        final Intent credential =
-                km.createConfirmDeviceCredentialIntent(null, null, userId);
-        // For safety, check null here in case users changed the setting after the checking.
-        if (credential == null) {
-            return;
-        }
-        final ActivityRecord activityRecord = targetStack.topRunningActivityLocked();
-        if (activityRecord != null) {
-            final IIntentSender target = mService.getIntentSenderLocked(
-                    ActivityManager.INTENT_SENDER_ACTIVITY,
-                    activityRecord.launchedFromPackage,
-                    activityRecord.launchedFromUid,
-                    activityRecord.userId,
-                    null, null, 0,
-                    new Intent[] { activityRecord.intent },
-                    new String[] { activityRecord.resolvedType },
-                    PendingIntent.FLAG_CANCEL_CURRENT |
-                            PendingIntent.FLAG_ONE_SHOT |
-                            PendingIntent.FLAG_IMMUTABLE,
-                    null);
-            credential.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
-            // Show confirm credentials activity.
-            startConfirmCredentialIntent(credential, null);
-        }
-    }
-
     void startConfirmCredentialIntent(Intent intent, Bundle optionsBundle) {
         intent.addFlags(FLAG_ACTIVITY_NEW_TASK |
                 FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS |
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 213041e..ef792b0 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -688,7 +688,7 @@
         mSettingsObserver = new SettingsObserver();
         createStreamStates();
 
-        mMediaFocusControl = new MediaFocusControl(mContext);
+        mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
 
         readAndSetLowRamDevice();
 
@@ -5581,6 +5581,10 @@
         return mMediaFocusControl.getCurrentAudioFocus();
     }
 
+    public int getFocusRampTimeMs(int focusGain, AudioAttributes attr) {
+        return mMediaFocusControl.getFocusRampTimeMs(focusGain, attr);
+    }
+
     private boolean readCameraSoundForced() {
         return SystemProperties.getBoolean("audio.camerasound.force", false) ||
                 mContext.getResources().getBoolean(
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index cc181141..5275c05 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -17,6 +17,7 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.media.AudioAttributes;
 import android.media.AudioFocusInfo;
 import android.media.AudioManager;
@@ -47,6 +48,7 @@
     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.
      */
@@ -62,6 +64,10 @@
      */
     private int mFocusLossReceived;
     /**
+     * whether this focus owner listener was notified when it lost focus
+     */
+    private boolean mFocusLossWasNotified;
+    /**
      * the audio attributes associated with the focus request
      */
     private final AudioAttributes mAttributes;
@@ -124,6 +130,10 @@
         return mCallingUid == uid;
     }
 
+    int getClientUid() {
+        return mCallingUid;
+    }
+
     String getClientId() {
         return mClientId;
     }
@@ -195,6 +205,7 @@
                 + " -- gain: " + focusGainToString()
                 + " -- flags: " + flagsToString(mGrantFlags)
                 + " -- loss: " + focusLossToString()
+                + " -- notified: " + mFocusLossWasNotified
                 + " -- uid: " + mCallingUid
                 + " -- attr: " + mAttributes);
     }
@@ -263,9 +274,9 @@
     /**
      * Called synchronized on MediaFocusControl.mAudioFocusLock
      */
-    void handleExternalFocusGain(int focusGain) {
+    void handleExternalFocusGain(int focusGain, final FocusRequester fr) {
         int focusLoss = focusLossForGainRequest(focusGain);
-        handleFocusLoss(focusLoss);
+        handleFocusLoss(focusLoss, fr);
     }
 
     /**
@@ -273,6 +284,7 @@
      */
     void handleFocusGain(int focusGain) {
         try {
+            final int oldLoss = mFocusLossReceived;
             mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
             mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(),
                     AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
@@ -282,8 +294,13 @@
                     Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
                         + mClientId);
                 }
-                fd.dispatchAudioFocusChange(focusGain, mClientId);
+                if (mFocusLossWasNotified) {
+                    fd.dispatchAudioFocusChange(focusGain, mClientId);
+                } else if (oldLoss == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
+                    mFocusController.unduckPlayers(this);
+                }
             }
+            mFocusLossWasNotified = false;
         } catch (android.os.RemoteException e) {
             Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
         }
@@ -292,10 +309,11 @@
     /**
      * Called synchronized on MediaFocusControl.mAudioFocusLock
      */
-    void handleFocusLoss(int focusLoss) {
+    void handleFocusLoss(int focusLoss, @Nullable final FocusRequester fr) {
         try {
             if (focusLoss != mFocusLossReceived) {
                 mFocusLossReceived = focusLoss;
+                mFocusLossWasNotified = false;
                 // 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
@@ -313,6 +331,27 @@
                             toAudioFocusInfo(), false /* wasDispatched */);
                     return;
                 }
+
+                // check enforcement by the framework
+                boolean handled = false;
+                if (focusLoss == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
+                        && MediaFocusControl.ENFORCE_DUCKING
+                        && fr != null) {
+                    // candidate for enforcement by the framework
+                    if (fr.mCallingUid != this.mCallingUid) {
+                        handled = mFocusController.duckPlayers(fr, this);
+                    } // else: the focus change is within the same app, so let the dispatching
+                      //       happen as if the framework was not involved.
+                }
+
+                if (handled) {
+                    if (DEBUG) {
+                        Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
+                            + " to " + mClientId + ", ducking implemented by framework");
+                    }
+                    return; // with mFocusLossWasNotified = false
+                }
+
                 final IAudioFocusDispatcher fd = mFocusDispatcher;
                 if (fd != null) {
                     if (DEBUG) {
@@ -321,6 +360,7 @@
                     }
                     mFocusController.notifyExtPolicyFocusLoss_syncAf(
                             toAudioFocusInfo(), true /* wasDispatched */);
+                    mFocusLossWasNotified = true;
                     fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
                 }
             }
@@ -330,7 +370,7 @@
     }
 
     AudioFocusInfo toAudioFocusInfo() {
-        return new AudioFocusInfo(mAttributes, mClientId, mPackageName,
+        return new AudioFocusInfo(mAttributes, mCallingUid, mClientId, mPackageName,
                 mFocusGainRequest, mFocusLossReceived, mGrantFlags);
     }
 }
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 206834e..a1c5653 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -40,16 +40,24 @@
  * @hide
  *
  */
-public class MediaFocusControl {
+public class MediaFocusControl implements PlayerFocusEnforcer {
 
     private static final String TAG = "MediaFocusControl";
 
+    /**
+     * set to true so the framework enforces ducking itself, without communicating to apps
+     * that they lost focus.
+     */
+    static final boolean ENFORCE_DUCKING = false;
+
     private final Context mContext;
     private final AppOpsManager mAppOps;
+    private PlayerFocusEnforcer mFocusEnforcer; // never null
 
-    protected MediaFocusControl(Context cntxt) {
+    protected MediaFocusControl(Context cntxt, PlayerFocusEnforcer pfe) {
         mContext = cntxt;
         mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mFocusEnforcer = pfe;
     }
 
     protected void dump(PrintWriter pw) {
@@ -58,6 +66,17 @@
         dumpFocusStack(pw);
     }
 
+    //=================================================================
+    // PlayerFocusEnforcer implementation
+    @Override
+    public boolean duckPlayers(FocusRequester winner, FocusRequester loser) {
+        return mFocusEnforcer.duckPlayers(winner, loser);
+    }
+
+    @Override
+    public void unduckPlayers(FocusRequester winner) {
+        mFocusEnforcer.unduckPlayers(winner);
+    }
 
     //==========================================================================================
     // AudioFocus
@@ -75,7 +94,7 @@
             if (!mFocusStack.empty()) {
                 // notify the current focus owner it lost focus after removing it from stack
                 final FocusRequester exFocusOwner = mFocusStack.pop();
-                exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS);
+                exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null);
                 exFocusOwner.release();
             }
         }
@@ -97,12 +116,12 @@
      * Focus is requested, propagate the associated loss throughout the stack.
      * @param focusGain the new focus gain that will later be added at the top of the stack
      */
-    private void propagateFocusLossFromGain_syncAf(int focusGain) {
+    private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr) {
         // going through the audio focus stack to signal new focus, traversing order doesn't
         // matter as all entries respond to the same external focus gain
         Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
         while(stackIterator.hasNext()) {
-            stackIterator.next().handleExternalFocusGain(focusGain);
+            stackIterator.next().handleExternalFocusGain(focusGain, fr);
         }
     }
 
@@ -237,7 +256,7 @@
             Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
                     new Exception());
             // no exclusive owner, push at top of stack, focus is granted, propagate change
-            propagateFocusLossFromGain_syncAf(nfr.getGainRequest());
+            propagateFocusLossFromGain_syncAf(nfr.getGainRequest(), nfr);
             mFocusStack.push(nfr);
             return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
         } else {
@@ -381,6 +400,38 @@
         }
     }
 
+    /**
+     * Return the volume ramp time expected before playback with the given AudioAttributes would
+     * start after gaining audio focus.
+     * @param attr attributes of the sound about to start playing
+     * @return time in ms
+     */
+    protected int getFocusRampTimeMs(int focusGain, AudioAttributes attr) {
+        switch (attr.getUsage()) {
+            case AudioAttributes.USAGE_MEDIA:
+            case AudioAttributes.USAGE_GAME:
+                return 1000;
+            case AudioAttributes.USAGE_ALARM:
+            case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
+            case AudioAttributes.USAGE_ASSISTANT:
+            case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
+            case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+                return 700;
+            case AudioAttributes.USAGE_VOICE_COMMUNICATION:
+            case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING:
+            case AudioAttributes.USAGE_NOTIFICATION:
+            case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+            case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+            case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+            case AudioAttributes.USAGE_NOTIFICATION_EVENT:
+            case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
+                return 500;
+            case AudioAttributes.USAGE_UNKNOWN:
+            default:
+                return 0;
+        }
+    }
+
     /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */
     protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
             IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) {
@@ -463,7 +514,7 @@
             } else {
                 // propagate the focus change through the stack
                 if (!mFocusStack.empty()) {
-                    propagateFocusLossFromGain_syncAf(focusChangeHint);
+                    propagateFocusLossFromGain_syncAf(focusChangeHint, nfr);
                 }
 
                 // push focus requester at the top of the audio focus stack
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index c6b2cf6..816d5fe 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -37,15 +37,16 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Class to receive and dispatch updates from AudioSystem about recording configurations.
  */
 public final class PlaybackActivityMonitor
-        implements AudioPlaybackConfiguration.PlayerDeathMonitor {
+        implements AudioPlaybackConfiguration.PlayerDeathMonitor, PlayerFocusEnforcer {
 
     public final static String TAG = "AudioService.PlaybackActivityMonitor";
-    private final static boolean DEBUG = false;
+    private final static boolean DEBUG = true;
 
     private ArrayList<PlayMonitorClient> mClients = new ArrayList<PlayMonitorClient>();
     // a public client is one that needs an anonymized version of the playback configurations, we
@@ -134,12 +135,18 @@
     }
 
     protected void dump(PrintWriter pw) {
+        // players
         pw.println("\nPlaybackActivityMonitor dump time: "
                 + DateFormat.getTimeInstance().format(new Date()));
         synchronized(mPlayerLock) {
             for (AudioPlaybackConfiguration conf : mPlayers.values()) {
                 conf.dump(pw);
             }
+            // ducked players
+            pw.println("\n  ducked player piids:");
+            for (int piid : mDuckedPlayers) {
+                pw.println(" " + piid);
+            }
         }
     }
 
@@ -211,7 +218,7 @@
             List<AudioPlaybackConfiguration> sysConfigs) {
         ArrayList<AudioPlaybackConfiguration> publicConfigs =
                 new ArrayList<AudioPlaybackConfiguration>();
-        // only add active anonymized configurations, 
+        // only add active anonymized configurations,
         for (AudioPlaybackConfiguration config : sysConfigs) {
             if (config.isActive()) {
                 publicConfigs.add(AudioPlaybackConfiguration.anonymizedCopy(config));
@@ -220,6 +227,82 @@
         return publicConfigs;
     }
 
+
+    //=================================================================
+    // PlayerFocusEnforcer implementation
+    private final ArrayList<Integer> mDuckedPlayers = new ArrayList<Integer>();
+
+    @Override
+    public boolean duckPlayers(FocusRequester winner, FocusRequester loser) {
+        if (DEBUG) {
+            Log.v(TAG, String.format("duckPlayers: uids winner=%d loser=%d",
+                    winner.getClientUid(), loser.getClientUid())); }
+        synchronized (mPlayerLock) {
+            if (mPlayers.isEmpty()) {
+                return true;
+            }
+            final Set<Integer> piidSet = mPlayers.keySet();
+            final Iterator<Integer> piidIterator = piidSet.iterator();
+            // find which players to duck
+            while (piidIterator.hasNext()) {
+                final Integer piid = piidIterator.next();
+                final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+                if (!winner.hasSameUid(apc.getClientUid())
+                        && loser.hasSameUid(apc.getClientUid())
+                        && apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED)
+                {
+                    if (mDuckedPlayers.contains(piid)) {
+                        if (DEBUG) { Log.v(TAG, "player " + piid + " already ducked"); }
+                    } else if (apc.getAudioAttributes().getContentType() ==
+                            AudioAttributes.CONTENT_TYPE_SPEECH) {
+                        // the player is speaking, ducking will make the speech unintelligible
+                        // so let the app handle it instead
+                        return false;
+                    } else {
+                        try {
+                            if (DEBUG) { Log.v(TAG, "ducking player " + piid); }
+                            //FIXME just a test before we have VolumeShape
+                            apc.getPlayerProxy().setPan(-1.0f);
+                            mDuckedPlayers.add(piid);
+                        } catch (Exception e) {
+                            Log.e(TAG, "Error ducking player " + piid, e);
+                            // something went wrong trying to duck, so let the app handle it
+                            // instead, it may know things we don't
+                            return false;
+                        }
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void unduckPlayers(FocusRequester winner) {
+        if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); }
+        synchronized (mPlayerLock) {
+            if (mDuckedPlayers.isEmpty()) {
+                return;
+            }
+            for (int piid : mDuckedPlayers) {
+                final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+                if (apc != null
+                        && winner.hasSameUid(apc.getClientUid())) {
+                    try {
+                        if (DEBUG) { Log.v(TAG, "unducking player" + piid); }
+                        //FIXME just a test before we have VolumeShape
+                        apc.getPlayerProxy().setPan(0.0f);
+                        mDuckedPlayers.remove(new Integer(piid));
+                    } catch (Exception e) {
+                        Log.e(TAG, "Error unducking player " + piid, e);
+                    }
+                } else {
+                    Log.e(TAG, "Error unducking player " + piid + ", player not found");
+                }
+            }
+        }
+    }
+
     //=================================================================
     // Track playback activity listeners
 
diff --git a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
new file mode 100644
index 0000000..acb4f0d
--- /dev/null
+++ b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
@@ -0,0 +1,31 @@
+/*
+ * 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 com.android.server.audio;
+
+public interface PlayerFocusEnforcer {
+
+    /**
+     * Ducks the players associated with the "loser" focus owner (i.e. same UID). Returns true if
+     * at least one active player was found and ducked, false otherwise.
+     * @param winner
+     * @param loser
+     * @return
+     */
+    public boolean duckPlayers(FocusRequester winner, FocusRequester loser);
+
+    public void unduckPlayers(FocusRequester winner);
+}
\ No newline at end of file