Merge "Force-use the pbuffer surface for destroy" into lmp-mr1-dev
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index d7b75db..2eccfbd 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -365,6 +365,9 @@
     SkASSERT(canvas);
     SkASSERT(env->IsInstanceOf(canvas, gCanvas_class));
     jlong canvasHandle = env->GetLongField(canvas, gCanvas_nativeInstanceID);
+    if (!canvasHandle) {
+        return NULL;
+    }
     SkCanvas* c = reinterpret_cast<android::Canvas*>(canvasHandle)->getSkCanvas();
     SkASSERT(c);
     return c;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3aaecc7..dec7f07 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1130,6 +1130,12 @@
                 android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
                 android:protectionLevel="signature" />
 
+    <!-- @hide Allows querying state of PersistentDataBlock
+   <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.ACCESS_PDB_STATE"
+                android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+                android:protectionLevel="signature" />
+
 
     <!-- =========================================== -->
     <!-- Permissions associated with audio capture -->
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
index c58563f..f58a765 100644
--- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -527,18 +527,14 @@
     @Override
     public Drawable mutate() {
         if (!mMutated && super.mutate() == this) {
-            mState.mutate();
+            final AnimatedStateListState newState = new AnimatedStateListState(mState, this, null);
+            setConstantState(newState);
             mMutated = true;
         }
 
         return this;
     }
 
-    @Override
-    AnimatedStateListState cloneConstantState() {
-        return new AnimatedStateListState(mState, this, null);
-    }
-
     /**
      * @hide
      */
@@ -557,29 +553,23 @@
 
         int[] mAnimThemeAttrs;
 
-        LongSparseLongArray mTransitions;
-        SparseIntArray mStateIds;
+        final LongSparseLongArray mTransitions;
+        final SparseIntArray mStateIds;
 
         AnimatedStateListState(@Nullable AnimatedStateListState orig,
                 @NonNull AnimatedStateListDrawable owner, @Nullable Resources res) {
             super(orig, owner, res);
 
             if (orig != null) {
-                // Perform a shallow copy and rely on mutate() to deep-copy.
                 mAnimThemeAttrs = orig.mAnimThemeAttrs;
-                mTransitions = orig.mTransitions;
-                mStateIds = orig.mStateIds;
+                mTransitions = orig.mTransitions.clone();
+                mStateIds = orig.mStateIds.clone();
             } else {
                 mTransitions = new LongSparseLongArray();
                 mStateIds = new SparseIntArray();
             }
         }
 
-        private void mutate() {
-            mTransitions = mTransitions.clone();
-            mStateIds = mStateIds.clone();
-        }
-
         int addTransition(int fromId, int toId, @NonNull Drawable anim, boolean reversible) {
             final int pos = super.addChild(anim);
             final long keyFromTo = generateTransitionKey(fromId, toId);
@@ -651,7 +641,7 @@
         }
     }
 
-    protected void setConstantState(@NonNull AnimatedStateListState state) {
+    void setConstantState(@NonNull AnimatedStateListState state) {
         super.setConstantState(state);
 
         mState = state;
@@ -660,7 +650,6 @@
     private AnimatedStateListDrawable(@Nullable AnimatedStateListState state, @Nullable Resources res) {
         super(null);
 
-        // Every animated state list drawable has its own constant state.
         final AnimatedStateListState newState = new AnimatedStateListState(state, this, res);
         setConstantState(newState);
         onStateChange(getState());
diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 28ada82..2ddf9df 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -342,17 +342,12 @@
     @Override
     public Drawable mutate() {
         if (!mMutated && super.mutate() == this) {
-            mAnimationState.mutate();
+            mAnimationState.mDurations = mAnimationState.mDurations.clone();
             mMutated = true;
         }
         return this;
     }
 
-    @Override
-    AnimationState cloneConstantState() {
-        return new AnimationState(mAnimationState, this, null);
-    }
-
     /**
      * @hide
      */
@@ -378,10 +373,6 @@
             }
         }
 
-        private void mutate() {
-            mDurations = mDurations.clone();
-        }
-
         @Override
         public Drawable newDrawable() {
             return new AnimationDrawable(this, null);
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 6d43a0c..326490f 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -536,7 +536,7 @@
         }
 
         if (schedule && animating) {
-            scheduleSelf(mAnimationRunnable, now + 1000 / 60);
+            scheduleSelf(mAnimationRunnable, now + 1000/60);
         }
     }
 
@@ -567,7 +567,6 @@
     @Override
     public Drawable mutate() {
         if (!mMutated && super.mutate() == this) {
-            mDrawableContainerState = cloneConstantState();
             mDrawableContainerState.mutate();
             mMutated = true;
         }
@@ -575,16 +574,6 @@
     }
 
     /**
-     * Returns a shallow copy of the container's constant state to be used as
-     * the base state for {@link #mutate()}.
-     *
-     * @return a shallow copy of the constant state
-     */
-    DrawableContainerState cloneConstantState() {
-        return mDrawableContainerState;
-    }
-
-    /**
      * @hide
      */
     public void clearMutated() {
@@ -844,7 +833,7 @@
             return false;
         }
 
-        private void mutate() {
+        final void mutate() {
             // No need to call createAllFutures, since future drawables will
             // mutate when they are prepared.
             final int N = mNumChildren;
diff --git a/graphics/java/android/graphics/drawable/LevelListDrawable.java b/graphics/java/android/graphics/drawable/LevelListDrawable.java
index dc41216..9e918f6 100644
--- a/graphics/java/android/graphics/drawable/LevelListDrawable.java
+++ b/graphics/java/android/graphics/drawable/LevelListDrawable.java
@@ -146,17 +146,13 @@
     @Override
     public Drawable mutate() {
         if (!mMutated && super.mutate() == this) {
-            mLevelListState.mutate();
+            mLevelListState.mLows = mLevelListState.mLows.clone();
+            mLevelListState.mHighs = mLevelListState.mHighs.clone();
             mMutated = true;
         }
         return this;
     }
 
-    @Override
-    LevelListState cloneConstantState() {
-        return new LevelListState(mLevelListState, this, null);
-    }
-
     /**
      * @hide
      */
@@ -173,7 +169,6 @@
             super(orig, owner, res);
 
             if (orig != null) {
-                // Perform a shallow copy and rely on mutate() to deep-copy.
                 mLows = orig.mLows;
                 mHighs = orig.mHighs;
             } else {
@@ -182,11 +177,6 @@
             }
         }
 
-        private void mutate() {
-            mLows = mLows.clone();
-            mHighs = mHighs.clone();
-        }
-
         public void addLevel(int low, int high, Drawable drawable) {
             int pos = addChild(drawable);
             mLows[pos] = low;
diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java
index eb6f94f..e7a8233 100644
--- a/graphics/java/android/graphics/drawable/StateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/StateListDrawable.java
@@ -24,8 +24,6 @@
 import java.io.IOException;
 import java.util.Arrays;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.Resources.Theme;
@@ -290,17 +288,20 @@
     @Override
     public Drawable mutate() {
         if (!mMutated && super.mutate() == this) {
-            mStateListState.mutate();
+            final int[][] sets = mStateListState.mStateSets;
+            final int count = sets.length;
+            mStateListState.mStateSets = new int[count][];
+            for (int i = 0; i < count; i++) {
+                final int[] set = sets[i];
+                if (set != null) {
+                    mStateListState.mStateSets[i] = set.clone();
+                }
+            }
             mMutated = true;
         }
         return this;
     }
 
-    @Override
-    StateListState cloneConstantState() {
-        return new StateListState(mStateListState, this, null);
-    }
-
     /**
      * @hide
      */
@@ -327,24 +328,25 @@
             super(orig, owner, res);
 
             if (orig != null) {
-                // Perform a shallow copy and rely on mutate() to deep-copy.
+                // Perform a deep copy.
+                final int[][] sets = orig.mStateSets;
+                final int count = sets.length;
+                mStateSets = new int[count][];
+                for (int i = 0; i < count; i++) {
+                    final int[] set = sets[i];
+                    if (set != null) {
+                        mStateSets[i] = set.clone();
+                    }
+                }
+
                 mThemeAttrs = orig.mThemeAttrs;
-                mStateSets = orig.mStateSets;
+                mStateSets = Arrays.copyOf(orig.mStateSets, orig.mStateSets.length);
             } else {
                 mThemeAttrs = null;
                 mStateSets = new int[getCapacity()][];
             }
         }
 
-        private void mutate() {
-            mThemeAttrs = mThemeAttrs != null ? mThemeAttrs.clone() : null;
-
-            final int[][] stateSets = new int[mStateSets.length][];
-            for (int i = mStateSets.length - 1; i >= 0; i--) {
-                stateSets[i] = mStateSets[i] != null ? mStateSets[i].clone() : null;
-            }
-        }
-
         int addStateSet(int[] stateSet, Drawable drawable) {
             final int pos = addChild(drawable);
             mStateSets[pos] = stateSet;
@@ -393,14 +395,13 @@
         onStateChange(getState());
     }
 
-    protected void setConstantState(@NonNull StateListState state) {
+    void setConstantState(StateListState state) {
         super.setConstantState(state);
 
         mStateListState = state;
     }
 
     private StateListDrawable(StateListState state, Resources res) {
-        // Every state list drawable has its own constant state.
         final StateListState newState = new StateListState(state, this, res);
         setConstantState(newState);
         onStateChange(getState());
@@ -410,7 +411,7 @@
      * This constructor exists so subclasses can avoid calling the default
      * constructor and setting up a StateListDrawable-specific constant state.
      */
-    StateListDrawable(@Nullable StateListState state) {
+    StateListDrawable(StateListState state) {
         if (state != null) {
             setConstantState(state);
         }
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index eb0948f..bbba2dd 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -105,8 +105,11 @@
  * display list. This function should remain in sync with the replay() function.
  */
 void RenderNode::output(uint32_t level) {
-    ALOGD("%*sStart display list (%p, %s, render=%d)", (level - 1) * 2, "", this,
-            getName(), isRenderable());
+    ALOGD("%*sStart display list (%p, %s%s%s%s)", (level - 1) * 2, "", this,
+            getName(),
+            (properties().hasShadow() ? ", casting shadow" : ""),
+            (isRenderable() ? "" : ", empty"),
+            (mLayer != NULL ? ", on HW Layer" : ""));
     ALOGD("%*s%s %d", level * 2, "", "Save",
             SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
 
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index b936d4b..31c4f22 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -570,7 +570,7 @@
     }
 
     bool hasShadow() const {
-        return getZ() >= 0.0f
+        return getZ() > 0.0f
                 && getOutline().getPath() != NULL
                 && getOutline().getAlpha() != 0.0f;
     }
diff --git a/libs/hwui/tests/Android.mk b/libs/hwui/tests/Android.mk
index 9622073..7bdce7f 100644
--- a/libs/hwui/tests/Android.mk
+++ b/libs/hwui/tests/Android.mk
@@ -19,6 +19,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
+LOCAL_CFLAGS += -Wno-unused-parameter
 LOCAL_CFLAGS += -DATRACE_TAG=ATRACE_TAG_VIEW -DLOG_TAG=\"OpenGLRenderer\"
 
 LOCAL_SRC_FILES:= \
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 3f08305..0642bcd 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -470,6 +470,49 @@
     public static final int FLAG_SHOW_UI_WARNINGS = 1 << 10;
 
     /**
+     * Adjusting the volume down from vibrated was prevented, display a hint in the UI.
+     * @hide
+     */
+    public static final int FLAG_SHOW_VIBRATE_HINT = 1 << 11;
+
+    private static final String[] FLAG_NAMES = {
+        "FLAG_SHOW_UI",
+        "FLAG_ALLOW_RINGER_MODES",
+        "FLAG_PLAY_SOUND",
+        "FLAG_REMOVE_SOUND_AND_VIBRATE",
+        "FLAG_VIBRATE",
+        "FLAG_FIXED_VOLUME",
+        "FLAG_BLUETOOTH_ABS_VOLUME",
+        "FLAG_SHOW_SILENT_HINT",
+        "FLAG_HDMI_SYSTEM_AUDIO_VOLUME",
+        "FLAG_ACTIVE_MEDIA_ONLY",
+        "FLAG_SHOW_UI_WARNINGS",
+        "FLAG_SHOW_VIBRATE_HINT",
+    };
+
+    /** @hide */
+    public static String flagsToString(int flags) {
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < FLAG_NAMES.length; i++) {
+            final int flag = 1 << i;
+            if ((flags & flag) != 0) {
+                if (sb.length() > 0) {
+                    sb.append(',');
+                }
+                sb.append(FLAG_NAMES[i]);
+                flags &= ~flag;
+            }
+        }
+        if (flags != 0) {
+            if (sb.length() > 0) {
+                sb.append(',');
+            }
+            sb.append(flags);
+        }
+        return sb.toString();
+    }
+
+    /**
      * Ringer mode that will be silent and will not vibrate. (This overrides the
      * vibrate setting.)
      *
@@ -857,7 +900,7 @@
     public int getRingerMode() {
         IAudioService service = getService();
         try {
-            return service.getRingerMode();
+            return service.getRingerModeExternal();
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in getRingerMode", e);
             return RINGER_MODE_NORMAL;
@@ -977,21 +1020,12 @@
      * @see #isVolumeFixed()
      */
     public void setRingerMode(int ringerMode) {
-        setRingerMode(ringerMode, true /*checkZen*/);
-    }
-
-    /**
-     * @see #setRingerMode(int)
-     * @param checkZen  Update zen mode if necessary to compensate.
-     * @hide
-     */
-    public void setRingerMode(int ringerMode, boolean checkZen) {
         if (!isValidRingerMode(ringerMode)) {
             return;
         }
         IAudioService service = getService();
         try {
-            service.setRingerMode(ringerMode, checkZen);
+            service.setRingerModeExternal(ringerMode, mContext.getOpPackageName());
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in setRingerMode", e);
         }
@@ -3308,6 +3342,31 @@
     }
 
     /**
+     * Only useful for volume controllers.
+     * @hide
+     */
+    public void setRingerModeInternal(int ringerMode) {
+        try {
+            getService().setRingerModeInternal(ringerMode, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error calling setRingerModeInternal", e);
+        }
+    }
+
+    /**
+     * Only useful for volume controllers.
+     * @hide
+     */
+    public int getRingerModeInternal() {
+        try {
+            return getService().getRingerModeInternal();
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error calling getRingerModeInternal", e);
+            return RINGER_MODE_NORMAL;
+        }
+    }
+
+    /**
      * Set Hdmi Cec system audio mode.
      *
      * @param on whether to be on system audio mode
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index a6cc493..d9586bc 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -38,4 +38,20 @@
 
     public abstract void adjustMasterVolumeForUid(int steps, int flags, String callingPackage,
             int uid);
+
+    public abstract void setRingerModeDelegate(RingerModeDelegate delegate);
+
+    public abstract int getRingerModeInternal();
+
+    public abstract void setRingerModeInternal(int ringerMode, String caller);
+
+    public interface RingerModeDelegate {
+        /** Called when external ringer mode is evaluated, returns the new internal ringer mode */
+        int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
+                int ringerModeInternal);
+
+        /** Called when internal ringer mode is evaluated, returns the new external ringer mode */
+        int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
+                int ringerModeExternal);
+    }
 }
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index b6b24a4..0175fd4 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -66,7 +66,6 @@
 import android.os.UserHandle;
 import android.os.Vibrator;
 import android.provider.Settings;
-import android.provider.Settings.Global;
 import android.provider.Settings.System;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
@@ -375,7 +374,8 @@
      * {@link AudioManager#RINGER_MODE_VIBRATE}.
      */
     // protected by mSettingsLock
-    private int mRingerMode;
+    private int mRingerMode;  // internal ringer mode, affects muting of underlying streams
+    private int mRingerModeExternal = -1;  // reported ringer mode to outside clients (AudioManager)
 
     /** @see System#MODE_RINGER_STREAMS_AFFECTED */
     private int mRingerModeAffectedStreams = 0;
@@ -532,6 +532,8 @@
 
     private static Long mLastDeviceConnectMsgTime = new Long(0);
 
+    private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate;
+
     ///////////////////////////////////////////////////////////////////////////
     // Construction
     ///////////////////////////////////////////////////////////////////////////
@@ -619,7 +621,7 @@
         // Call setRingerModeInt() to apply correct mute
         // state on streams affected by ringer mode.
         mRingerModeMutedStreams = 0;
-        setRingerModeInt(getRingerMode(), false);
+        setRingerModeInt(getRingerModeInternal(), false);
 
         // Register for device connection intent broadcasts.
         IntentFilter intentFilter =
@@ -829,7 +831,7 @@
         if (updateVolumes) {
             mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias]);
             // apply stream mute states according to new value of mRingerModeAffectedStreams
-            setRingerModeInt(getRingerMode(), false);
+            setRingerModeInt(getRingerModeInternal(), false);
             sendMsg(mAudioHandler,
                     MSG_SET_ALL_VOLUMES,
                     SENDMSG_QUEUE,
@@ -883,6 +885,9 @@
         }
         synchronized(mSettingsLock) {
             mRingerMode = ringerMode;
+            if (mRingerModeExternal == -1) {
+                mRingerModeExternal = mRingerMode;
+            }
 
             // System.VIBRATE_ON is not used any more but defaults for mVibrateSetting
             // are still needed while setVibrateSetting() and getVibrateSetting() are being
@@ -1067,7 +1072,7 @@
         // or the stream type is one that is affected by ringer modes
         if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
                 (streamTypeAlias == getMasterStreamType())) {
-            int ringerMode = getRingerMode();
+            int ringerMode = getRingerModeInternal();
             // do not vibrate if already in vibrate mode
             if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
                 flags &= ~AudioManager.FLAG_VIBRATE;
@@ -1080,6 +1085,10 @@
             if ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {
                 flags |= AudioManager.FLAG_SHOW_SILENT_HINT;
             }
+            // If suppressing a volume down adjustment in vibrate mode, display the UI hint
+            if ((result & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) {
+                flags |= AudioManager.FLAG_SHOW_VIBRATE_HINT;
+            }
         }
 
         int oldIndex = mStreamStates[streamType].getIndex(device);
@@ -1206,7 +1215,7 @@
             } else {
                 newRingerMode = AudioManager.RINGER_MODE_NORMAL;
             }
-            setRingerMode(newRingerMode, false /*checkZen*/);
+            setRingerMode(newRingerMode, TAG + ".onSetStreamVolume", false /*external*/);
         }
     }
 
@@ -1769,8 +1778,15 @@
                 : 0, UserHandle.getCallingUserId(), null, PERSIST_DELAY);
     }
 
-    /** @see AudioManager#getRingerMode() */
-    public int getRingerMode() {
+    @Override
+    public int getRingerModeExternal() {
+        synchronized(mSettingsLock) {
+            return mRingerModeExternal;
+        }
+    }
+
+    @Override
+    public int getRingerModeInternal() {
         synchronized(mSettingsLock) {
             return mRingerMode;
         }
@@ -1787,36 +1803,57 @@
         return ringerMode >= 0 && ringerMode <= AudioManager.RINGER_MODE_MAX;
     }
 
-    /** @see AudioManager#setRingerMode(int) */
-    public void setRingerMode(int ringerMode, boolean checkZen) {
+    public void setRingerModeExternal(int ringerMode, String caller) {
+        setRingerMode(ringerMode, caller, true /*external*/);
+    }
+
+    public void setRingerModeInternal(int ringerMode, String caller) {
+        enforceSelfOrSystemUI("setRingerModeInternal");
+        setRingerMode(ringerMode, caller, false /*external*/);
+    }
+
+    private void setRingerMode(int ringerMode, String caller, boolean external) {
         if (mUseFixedVolume || isPlatformTelevision()) {
             return;
         }
+        if (caller == null || caller.length() == 0) {
+            throw new IllegalArgumentException("Bad caller: " + caller);
+        }
         ensureValidRingerMode(ringerMode);
         if ((ringerMode == AudioManager.RINGER_MODE_VIBRATE) && !mHasVibrator) {
             ringerMode = AudioManager.RINGER_MODE_SILENT;
         }
-        if (checkZen) {
-            checkZen(ringerMode);
-        }
-        if (ringerMode != getRingerMode()) {
-            setRingerModeInt(ringerMode, true);
-            // Send sticky broadcast
-            broadcastRingerMode(ringerMode);
+        final int ringerModeInternal = getRingerModeInternal();
+        final int ringerModeExternal = getRingerModeExternal();
+        if (external) {
+            setRingerModeExt(ringerMode);
+            if (mRingerModeDelegate != null) {
+                ringerMode = mRingerModeDelegate.onSetRingerModeExternal(ringerModeExternal,
+                        ringerMode, caller, ringerModeInternal);
+            }
+            if (ringerMode != ringerModeInternal) {
+                setRingerModeInt(ringerMode, true /*persist*/);
+            }
+        } else /*internal*/ {
+            if (ringerMode != ringerModeInternal) {
+                setRingerModeInt(ringerMode, true /*persist*/);
+                mVolumeController.postInternalRingerModeChanged(ringerMode);
+            }
+            if (mRingerModeDelegate != null) {
+                ringerMode = mRingerModeDelegate.onSetRingerModeInternal(ringerModeInternal,
+                        ringerMode, caller, ringerModeExternal);
+            }
+            setRingerModeExt(ringerMode);
         }
     }
 
-    private void checkZen(int ringerMode) {
-        // leave zen when callers set ringer-mode = normal or vibrate
-        final int zen = Global.getInt(mContentResolver, Global.ZEN_MODE, Global.ZEN_MODE_OFF);
-        if (ringerMode != AudioManager.RINGER_MODE_SILENT && zen != Global.ZEN_MODE_OFF) {
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                Global.putInt(mContentResolver, Global.ZEN_MODE, Global.ZEN_MODE_OFF);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
+    private void setRingerModeExt(int ringerMode) {
+        synchronized(mSettingsLock) {
+            if (ringerMode == mRingerModeExternal) return;
+            mRingerModeExternal = ringerMode;
         }
+        // Send sticky broadcast
+        broadcastRingerMode(ringerMode);
     }
 
     private void setRingerModeInt(int ringerMode, boolean persist) {
@@ -1829,34 +1866,35 @@
         // Unmute stream if previously muted by ringer mode and ringer mode
         // is RINGER_MODE_NORMAL or stream is not affected by ringer mode.
         int numStreamTypes = AudioSystem.getNumStreamTypes();
+        final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE
+                || ringerMode == AudioManager.RINGER_MODE_SILENT;
         for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
-            if (isStreamMutedByRingerMode(streamType)) {
-                if (!isStreamAffectedByRingerMode(streamType) ||
-                    ringerMode == AudioManager.RINGER_MODE_NORMAL) {
-                    // ring and notifications volume should never be 0 when not silenced
-                    // on voice capable devices or devices that support vibration
-                    if ((isPlatformVoice() || mHasVibrator) &&
-                            mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) {
-                        synchronized (VolumeStreamState.class) {
-                            Set set = mStreamStates[streamType].mIndex.entrySet();
-                            Iterator i = set.iterator();
-                            while (i.hasNext()) {
-                                Map.Entry entry = (Map.Entry)i.next();
-                                if ((Integer)entry.getValue() == 0) {
-                                    entry.setValue(10);
-                                }
+            final boolean isMuted = isStreamMutedByRingerMode(streamType);
+            final boolean shouldMute = ringerModeMute && isStreamAffectedByRingerMode(streamType);
+            if (isMuted == shouldMute) continue;
+            if (!shouldMute) {
+                // unmute
+                // ring and notifications volume should never be 0 when not silenced
+                // on voice capable devices or devices that support vibration
+                if ((isPlatformVoice() || mHasVibrator) &&
+                        mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) {
+                    synchronized (VolumeStreamState.class) {
+                        Set set = mStreamStates[streamType].mIndex.entrySet();
+                        Iterator i = set.iterator();
+                        while (i.hasNext()) {
+                            Map.Entry entry = (Map.Entry)i.next();
+                            if ((Integer)entry.getValue() == 0) {
+                                entry.setValue(10);
                             }
                         }
                     }
-                    mStreamStates[streamType].mute(null, false);
-                    mRingerModeMutedStreams &= ~(1 << streamType);
                 }
+                mStreamStates[streamType].mute(null, false);
+                mRingerModeMutedStreams &= ~(1 << streamType);
             } else {
-                if (isStreamAffectedByRingerMode(streamType) &&
-                    ringerMode != AudioManager.RINGER_MODE_NORMAL) {
-                   mStreamStates[streamType].mute(null, true);
-                   mRingerModeMutedStreams |= (1 << streamType);
-               }
+                // mute
+                mStreamStates[streamType].mute(null, true);
+                mRingerModeMutedStreams |= (1 << streamType);
             }
         }
 
@@ -1888,10 +1926,10 @@
         switch (getVibrateSetting(vibrateType)) {
 
             case AudioManager.VIBRATE_SETTING_ON:
-                return getRingerMode() != AudioManager.RINGER_MODE_SILENT;
+                return getRingerModeInternal() != AudioManager.RINGER_MODE_SILENT;
 
             case AudioManager.VIBRATE_SETTING_ONLY_SILENT:
-                return getRingerMode() == AudioManager.RINGER_MODE_VIBRATE;
+                return getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE;
 
             case AudioManager.VIBRATE_SETTING_OFF:
                 // return false, even for incoming calls
@@ -2352,7 +2390,7 @@
 
         // apply new ringer mode before checking volume for alias streams so that streams
         // muted by ringer mode have the correct volume
-        setRingerModeInt(getRingerMode(), false);
+        setRingerModeInt(getRingerModeInternal(), false);
 
         checkAllFixedVolumeDevices();
         checkAllAliasStreamVolumes();
@@ -3003,7 +3041,7 @@
      */
     private int checkForRingerModeChange(int oldIndex, int direction,  int step) {
         int result = FLAG_ADJUST_VOLUME;
-        int ringerMode = getRingerMode();
+        int ringerMode = getRingerModeInternal();
 
         switch (ringerMode) {
         case RINGER_MODE_NORMAL:
@@ -3037,6 +3075,8 @@
                 if (VOLUME_SETS_RINGER_MODE_SILENT
                         && mPrevVolDirection != AudioManager.ADJUST_LOWER) {
                     ringerMode = RINGER_MODE_SILENT;
+                } else {
+                    result |= AudioManager.FLAG_SHOW_VIBRATE_HINT;
                 }
             } else if (direction == AudioManager.ADJUST_RAISE) {
                 ringerMode = RINGER_MODE_NORMAL;
@@ -3062,7 +3102,7 @@
             break;
         }
 
-        setRingerMode(ringerMode, false /*checkZen*/);
+        setRingerMode(ringerMode, TAG + ".checkForRingerModeChange", false /*external*/);
 
         mPrevVolDirection = direction;
 
@@ -4136,7 +4176,7 @@
                 case MSG_PERSIST_RINGER_MODE:
                     // note that the value persisted is the current ringer mode, not the
                     // value of ringer mode as of the time the request was made to persist
-                    persistRingerMode(getRingerMode());
+                    persistRingerMode(getRingerModeInternal());
                     break;
 
                 case MSG_MEDIA_SERVER_DIED:
@@ -4188,7 +4228,7 @@
                     }
 
                     // Restore ringer mode
-                    setRingerModeInt(getRingerMode(), false);
+                    setRingerModeInt(getRingerModeInternal(), false);
 
                     // Restore master volume
                     restoreMasterVolume();
@@ -4358,7 +4398,7 @@
                      * Ensure all stream types that should be affected by ringer mode
                      * are in the proper state.
                      */
-                    setRingerModeInt(getRingerMode(), false);
+                    setRingerModeInt(getRingerModeInternal(), false);
                 }
                 readDockAudioSettings(mContentResolver);
             }
@@ -5110,7 +5150,7 @@
                                     (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
                         }
                         // take new state into account for streams muted by ringer mode
-                        setRingerModeInt(getRingerMode(), false);
+                        setRingerModeInt(getRingerModeInternal(), false);
                     }
 
                     sendMsg(mAudioHandler,
@@ -5451,11 +5491,13 @@
 
     private void dumpRingerMode(PrintWriter pw) {
         pw.println("\nRinger mode: ");
-        pw.println("- mode: "+RINGER_MODE_NAMES[mRingerMode]);
+        pw.println("- mode (internal) = " + RINGER_MODE_NAMES[mRingerMode]);
+        pw.println("- mode (external) = " + RINGER_MODE_NAMES[mRingerModeExternal]);
         pw.print("- ringer mode affected streams = 0x");
         pw.println(Integer.toHexString(mRingerModeAffectedStreams));
         pw.print("- ringer mode muted streams = 0x");
         pw.println(Integer.toHexString(mRingerModeMutedStreams));
+        pw.print("- delegate = "); pw.println(mRingerModeDelegate);
     }
 
     @Override
@@ -5477,6 +5519,7 @@
         pw.print("  mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
         pw.print("  mMusicActiveMs="); pw.println(mMusicActiveMs);
         pw.print("  mMcc="); pw.println(mMcc);
+        pw.print("  mHasVibrator="); pw.println(mHasVibrator);
     }
 
     private static String safeMediaVolumeStateToString(Integer state) {
@@ -5668,6 +5711,16 @@
                 Log.w(TAG, "Error calling dismiss", e);
             }
         }
+
+        public void postInternalRingerModeChanged(int mode) {
+            if (mController == null)
+                return;
+            try {
+                mController.internalRingerModeChanged(mode);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error calling internalRingerModeChanged", e);
+            }
+        }
     }
 
     /**
@@ -5675,6 +5728,13 @@
      * LocalServices.
      */
     final class AudioServiceInternal extends AudioManagerInternal {
+        @Override
+        public void setRingerModeDelegate(RingerModeDelegate delegate) {
+            mRingerModeDelegate = delegate;
+            if (mRingerModeDelegate != null) {
+                setRingerModeInternal(getRingerModeInternal(), TAG + ".setRingerModeDelegate");
+            }
+        }
 
         @Override
         public void adjustSuggestedStreamVolumeForUid(int streamType, int direction, int flags,
@@ -5701,6 +5761,16 @@
                 int uid) {
             adjustMasterVolume(steps, flags, callingPackage, uid);
         }
+
+        @Override
+        public int getRingerModeInternal() {
+            return AudioService.this.getRingerModeInternal();
+        }
+
+        @Override
+        public void setRingerModeInternal(int ringerMode, String caller) {
+            AudioService.this.setRingerModeInternal(ringerMode, caller);
+        }
     }
 
     //==========================================================================================
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index b691447..4d8aa76 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -77,9 +77,13 @@
 
     void setMicrophoneMute(boolean on, String callingPackage);
 
-    void setRingerMode(int ringerMode, boolean checkZen);
+    void setRingerModeExternal(int ringerMode, String caller);
 
-    int getRingerMode();
+    void setRingerModeInternal(int ringerMode, String caller);
+
+    int getRingerModeExternal();
+
+    int getRingerModeInternal();
 
     boolean isValidRingerMode(int ringerMode);
 
diff --git a/media/java/android/media/IVolumeController.aidl b/media/java/android/media/IVolumeController.aidl
index e3593a6..c31d80c 100644
--- a/media/java/android/media/IVolumeController.aidl
+++ b/media/java/android/media/IVolumeController.aidl
@@ -34,4 +34,6 @@
     void setLayoutDirection(int layoutDirection);
 
     void dismiss();
+
+    void internalRingerModeChanged(int mode);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 9246f4d..54eb18c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -53,6 +53,7 @@
 import android.graphics.ColorFilter;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
@@ -267,6 +268,7 @@
     private UnlockMethodCache mUnlockMethodCache;
     private DozeServiceHost mDozeServiceHost;
     private boolean mScreenOnComingFromTouch;
+    private PointF mScreenOnTouchLocation;
 
     int mPixelFormat;
     Object mQueueLock = new Object();
@@ -1028,6 +1030,12 @@
         }
     }
 
+    public void onInternalRingerModeChanged() {
+        if (mIconPolicy != null) {
+            mIconPolicy.updateVolumeZen();
+        }
+    }
+
     private void startKeyguard() {
         KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
         mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
@@ -3705,7 +3713,7 @@
         }
         boolean animate = !mDozing && mDozeScrimController.isPulsing();
         mNotificationPanel.setDozing(mDozing, animate);
-        mStackScroller.setDark(mDozing, animate);
+        mStackScroller.setDark(mDozing, animate, mScreenOnTouchLocation);
         mScrimController.setDozing(mDozing);
         mDozeScrimController.setDozing(mDozing, animate);
     }
@@ -3961,6 +3969,7 @@
     public void onScreenTurnedOff() {
         mScreenOnFromKeyguard = false;
         mScreenOnComingFromTouch = false;
+        mScreenOnTouchLocation = null;
         mStackScroller.setAnimationsEnabled(false);
         updateVisibleToUser();
     }
@@ -4083,14 +4092,13 @@
         return !mNotificationData.getActiveNotifications().isEmpty();
     }
 
-    public void wakeUpIfDozing(long time, boolean fromTouch) {
+    public void wakeUpIfDozing(long time, MotionEvent event) {
         if (mDozing && mDozeScrimController.isPulsing()) {
             PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
             pm.wakeUp(time);
-            if (fromTouch) {
-                mScreenOnComingFromTouch = true;
-                mNotificationPanel.setTouchDisabled(false);
-            }
+            mScreenOnComingFromTouch = true;
+            mScreenOnTouchLocation = new PointF(event.getX(), event.getY());
+            mNotificationPanel.setTouchDisabled(false);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 60d23ce..94401d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -208,7 +208,7 @@
         }
     }
 
-    private final void updateVolumeZen() {
+    public final void updateVolumeZen() {
         AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
 
         boolean zenVisible = false;
@@ -230,7 +230,7 @@
         }
 
         if (mZen != Global.ZEN_MODE_NO_INTERRUPTIONS &&
-                audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
+                audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) {
             volumeVisible = true;
             volumeIconId = R.drawable.stat_sys_ringer_vibrate;
             volumeDescription = mContext.getString(R.string.accessibility_ringer_vibrate);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 47ec08d..a96f4e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -173,7 +173,7 @@
             intercept = mDragDownHelper.onInterceptTouchEvent(ev);
             // wake up on a touch down event, if dozing
             if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                mService.wakeUpIfDozing(ev.getEventTime(), true);
+                mService.wakeUpIfDozing(ev.getEventTime(), ev);
             }
         }
         if (!intercept) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 415eb27..37ed7d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -157,8 +157,9 @@
         if (mRegistered) {
             mContext.unregisterReceiver(mReceiver);
         }
-        mContext.registerReceiverAsUser(mReceiver, new UserHandle(mUserId),
-                new IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED), null, null);
+        final IntentFilter filter = new IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+        filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
+        mContext.registerReceiverAsUser(mReceiver, new UserHandle(mUserId), filter, null, null);
         mRegistered = true;
         mSetupObserver.register();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
index 3d4cda6..753a7f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
@@ -34,6 +34,7 @@
     boolean hasDelays;
     boolean hasGoToFullShadeEvent;
     boolean hasDarkEvent;
+    int darkAnimationOriginIndex;
 
     public AnimationFilter animateAlpha() {
         animateAlpha = true;
@@ -94,14 +95,16 @@
         reset();
         int size = events.size();
         for (int i = 0; i < size; i++) {
+            NotificationStackScrollLayout.AnimationEvent ev = events.get(i);
             combineFilter(events.get(i).filter);
-            if (events.get(i).animationType ==
+            if (ev.animationType ==
                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE) {
                 hasGoToFullShadeEvent = true;
             }
-            if (events.get(i).animationType ==
+            if (ev.animationType ==
                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_DARK) {
                 hasDarkEvent = true;
+                darkAnimationOriginIndex = ev.darkAnimationOriginIndex;
             }
         }
     }
@@ -132,5 +135,7 @@
         hasDelays = false;
         hasGoToFullShadeEvent = false;
         hasDarkEvent = false;
+        darkAnimationOriginIndex =
+                NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 16ff3dc..2a393bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -16,10 +16,12 @@
 
 package com.android.systemui.statusbar.stack;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.PointF;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -149,6 +151,7 @@
     private boolean mDimmedNeedsAnimation;
     private boolean mHideSensitiveNeedsAnimation;
     private boolean mDarkNeedsAnimation;
+    private int mDarkAnimationOriginIndex;
     private boolean mActivateNeedsAnimation;
     private boolean mGoToFullShadeNeedsAnimation;
     private boolean mIsExpanded = true;
@@ -210,6 +213,7 @@
         }
     };
     private PhoneStatusBar mPhoneStatusBar;
+    private int[] mTempInt2 = new int[2];
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -587,10 +591,38 @@
         return getChildAtPosition(ev.getX(), ev.getY());
     }
 
+    public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
+        getLocationOnScreen(mTempInt2);
+        float localTouchY = touchY - mTempInt2[1];
+
+        ExpandableView closestChild = null;
+        float minDist = Float.MAX_VALUE;
+
+        // find the view closest to the location, accounting for GONE views
+        final int count = getChildCount();
+        for (int childIdx = 0; childIdx < count; childIdx++) {
+            ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
+            if (slidingChild.getVisibility() == GONE
+                    || slidingChild instanceof StackScrollerDecorView
+                    || slidingChild == mSpeedBumpView) {
+                continue;
+            }
+            float childTop = slidingChild.getTranslationY();
+            float top = childTop + slidingChild.getClipTopAmount();
+            float bottom = childTop + slidingChild.getActualHeight();
+
+            float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
+            if (dist < minDist) {
+                closestChild = slidingChild;
+                minDist = dist;
+            }
+        }
+        return closestChild;
+    }
+
     public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
-        int[] location = new int[2];
-        getLocationOnScreen(location);
-        return getChildAtPosition(touchX - location[0], touchY - location[1]);
+        getLocationOnScreen(mTempInt2);
+        return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
     }
 
     public ExpandableView getChildAtPosition(float touchX, float touchY) {
@@ -1818,8 +1850,9 @@
 
     private void generateDarkEvent() {
         if (mDarkNeedsAnimation) {
-            mAnimationEvents.add(
-                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK));
+            AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
+            ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
+            mAnimationEvents.add(ev);
         }
         mDarkNeedsAnimation = false;
     }
@@ -2151,7 +2184,7 @@
         mEmptyShadeView.setInvisible();
         mGoToFullShadeNeedsAnimation = true;
         mGoToFullShadeDelay = delay;
-        mNeedsAnimation =  true;
+        mNeedsAnimation = true;
         requestChildrenUpdate();
     }
 
@@ -2182,15 +2215,46 @@
     /**
      * See {@link AmbientState#setDark}.
      */
-    public void setDark(boolean dark, boolean animate) {
+    public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
         mAmbientState.setDark(dark);
         if (animate && mAnimationsEnabled) {
             mDarkNeedsAnimation = true;
+            mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
             mNeedsAnimation =  true;
         }
         requestChildrenUpdate();
     }
 
+    private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
+        if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
+            return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
+        }
+        if (screenLocation.y > getBottomMostNotificationBottom()) {
+            return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
+        }
+        View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
+        if (child != null) {
+            return getNotGoneIndex(child);
+        } else {
+            return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
+        }
+    }
+
+    private int getNotGoneIndex(View child) {
+        int count = getChildCount();
+        int notGoneIndex = 0;
+        for (int i = 0; i < count; i++) {
+            View v = getChildAt(i);
+            if (child == v) {
+                return notGoneIndex;
+            }
+            if (v.getVisibility() != View.GONE) {
+                notGoneIndex++;
+            }
+        }
+        return -1;
+    }
+
     public void setDismissView(DismissView dismissView) {
         mDismissView = dismissView;
         addView(mDismissView);
@@ -2556,12 +2620,16 @@
         static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
         static final int ANIMATION_TYPE_EVERYTHING = 13;
 
+        static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
+        static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
+
         final long eventStartTime;
         final View changingView;
         final int animationType;
         final AnimationFilter filter;
         final long length;
         View viewAfterChangingView;
+        int darkAnimationOriginIndex;
 
         AnimationEvent(View view, int type) {
             this(view, type, LENGTHS[type]);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 05077bf..b027787 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -315,7 +315,17 @@
     }
 
     private long calculateDelayDark(StackScrollState.ViewState viewState) {
-        return viewState.notGoneIndex * ANIMATION_DELAY_PER_ELEMENT_DARK;
+        int referenceIndex;
+        if (mAnimationFilter.darkAnimationOriginIndex ==
+                NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
+            referenceIndex = 0;
+        } else if (mAnimationFilter.darkAnimationOriginIndex ==
+                NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
+            referenceIndex = mHostLayout.getNotGoneChildCount() - 1;
+        } else {
+            referenceIndex = mAnimationFilter.darkAnimationOriginIndex;
+        }
+        return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
     }
 
     private long calculateDelayGoToFullShade(StackScrollState.ViewState viewState) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/IconPulser.java b/packages/SystemUI/src/com/android/systemui/volume/IconPulser.java
new file mode 100644
index 0000000..9438af1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/IconPulser.java
@@ -0,0 +1,48 @@
+/*
+ * 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 com.android.systemui.volume;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+public class IconPulser {
+    private static final float PULSE_SCALE = 1.1f;
+
+    private final Interpolator mFastOutSlowInInterpolator;
+
+    public IconPulser(Context context) {
+        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+                android.R.interpolator.fast_out_slow_in);
+    }
+
+    public void start(final View target) {
+        if (target == null || target.getScaleX() != 1) return;  // n/a, or already running
+        target.animate().cancel();
+        target.animate().scaleX(PULSE_SCALE).scaleY(PULSE_SCALE)
+                .setInterpolator(mFastOutSlowInInterpolator)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        target.animate().scaleX(1).scaleY(1).setListener(null);
+                    }
+                });
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index 351911c..1fe4698 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -124,6 +124,8 @@
     private static final int MSG_ZEN_MODE_AVAILABLE_CHANGED = 13;
     private static final int MSG_USER_ACTIVITY = 14;
     private static final int MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED = 15;
+    private static final int MSG_ZEN_MODE_CHANGED = 16;
+    private static final int MSG_INTERNAL_RINGER_MODE_CHANGED = 17;
 
     // Pseudo stream type for master volume
     private static final int STREAM_MASTER = -100;
@@ -169,6 +171,8 @@
     private final ViewGroup mSliderPanel;
     /** The zen mode configuration panel view */
     private ZenModePanel mZenPanel;
+    /** The component currently suppressing notification stream effects */
+    private ComponentName mNotificationEffectsSuppressor;
 
     private Callback mCallback;
 
@@ -178,6 +182,7 @@
     private SparseArray<StreamControl> mStreamControls;
     private final AccessibilityManager mAccessibilityManager;
     private final SecondaryIconTransition mSecondaryIconTransition;
+    private final IconPulser mIconPulser;
 
     private enum StreamResources {
         BluetoothSCOStream(AudioManager.STREAM_BLUETOOTH_SCO,
@@ -188,7 +193,7 @@
         RingerStream(AudioManager.STREAM_RING,
                 R.string.volume_icon_description_ringer,
                 com.android.systemui.R.drawable.ic_ringer_audible,
-                com.android.systemui.R.drawable.ic_ringer_vibrate,
+                com.android.systemui.R.drawable.ic_ringer_mute,
                 false),
         VoiceStream(AudioManager.STREAM_VOICE_CALL,
                 R.string.volume_icon_description_incall,
@@ -208,7 +213,7 @@
         NotificationStream(AudioManager.STREAM_NOTIFICATION,
                 R.string.volume_icon_description_notification,
                 com.android.systemui.R.drawable.ic_ringer_audible,
-                com.android.systemui.R.drawable.ic_ringer_vibrate,
+                com.android.systemui.R.drawable.ic_ringer_mute,
                 true),
         // for now, use media resources for master volume
         MasterStream(STREAM_MASTER,
@@ -268,6 +273,7 @@
     // Synchronize when accessing this
     private ToneGenerator mToneGenerators[];
     private Vibrator mVibrator;
+    private boolean mHasVibrator;
 
     private static AlertDialog sSafetyWarning;
     private static Object sSafetyWarningLock = new Object();
@@ -354,6 +360,7 @@
         mAccessibilityManager = (AccessibilityManager) context.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
         mSecondaryIconTransition = new SecondaryIconTransition();
+        mIconPulser = new IconPulser(context);
 
         // For now, only show master volume if master volume is supported
         final Resources res = context.getResources();
@@ -435,10 +442,12 @@
 
         mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+        mHasVibrator = mVibrator != null && mVibrator.hasVibrator();
         mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
 
         if (mZenController != null && !useMasterVolume) {
             mZenModeAvailable = mZenController.isZenAvailable();
+            mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor();
             mZenController.addCallback(mZenCallback);
         }
 
@@ -470,8 +479,10 @@
         pw.print("  mTag="); pw.println(mTag);
         pw.print("  mRingIsSilent="); pw.println(mRingIsSilent);
         pw.print("  mVoiceCapable="); pw.println(mVoiceCapable);
+        pw.print("  mHasVibrator="); pw.println(mHasVibrator);
         pw.print("  mZenModeAvailable="); pw.println(mZenModeAvailable);
         pw.print("  mZenPanelExpanded="); pw.println(mZenPanelExpanded);
+        pw.print("  mNotificationEffectsSuppressor="); pw.println(mNotificationEffectsSuppressor);
         pw.print("  mTimeoutDelay="); pw.println(mTimeoutDelay);
         pw.print("  mDisabledAlpha="); pw.println(mDisabledAlpha);
         pw.print("  mLastRingerMode="); pw.println(mLastRingerMode);
@@ -639,16 +650,19 @@
             sc.iconRes = streamRes.iconRes;
             sc.iconMuteRes = streamRes.iconMuteRes;
             sc.icon.setImageResource(sc.iconRes);
-            sc.icon.setClickable(isNotification);
+            sc.icon.setClickable(isNotification && mHasVibrator);
             if (isNotification) {
-                sc.icon.setSoundEffectsEnabled(false);
-                sc.icon.setOnClickListener(new OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        resetTimeout();
-                        toggle(sc);
-                    }
-                });
+                if (mHasVibrator) {
+                    sc.icon.setSoundEffectsEnabled(false);
+                    sc.iconMuteRes = com.android.systemui.R.drawable.ic_ringer_vibrate;
+                    sc.icon.setOnClickListener(new OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            resetTimeout();
+                            toggleRinger(sc);
+                        }
+                    });
+                }
                 sc.iconSuppressedRes = com.android.systemui.R.drawable.ic_ringer_mute;
             }
             sc.seekbarView = (SeekBar) sc.group.findViewById(com.android.systemui.R.id.seekbar);
@@ -681,12 +695,13 @@
         }
     }
 
-    private void toggle(StreamControl sc) {
-        if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL) {
-            mAudioManager.setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
+    private void toggleRinger(StreamControl sc) {
+        if (!mHasVibrator) return;
+        if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_NORMAL) {
+            mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_VIBRATE);
             postVolumeChanged(sc.streamType, AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE);
         } else {
-            mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+            mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL);
             postVolumeChanged(sc.streamType, AudioManager.FLAG_PLAY_SOUND);
         }
     }
@@ -710,7 +725,7 @@
 
     private void updateSliderProgress(StreamControl sc, int progress) {
         final boolean isRinger = isNotificationOrRing(sc.streamType);
-        if (isRinger && mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) {
+        if (isRinger && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) {
             progress = mLastRingerProgress;
         }
         if (progress < 0) {
@@ -723,21 +738,30 @@
     }
 
     private void updateSliderIcon(StreamControl sc, boolean muted) {
+        ComponentName suppressor = null;
         if (isNotificationOrRing(sc.streamType)) {
-            int ringerMode = mAudioManager.getRingerMode();
+            suppressor = mNotificationEffectsSuppressor;
+            int ringerMode = mAudioManager.getRingerModeInternal();
             if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
                 ringerMode = mLastRingerMode;
             } else {
                 mLastRingerMode = ringerMode;
             }
-            muted = ringerMode == AudioManager.RINGER_MODE_VIBRATE;
+            if (mHasVibrator) {
+                muted = ringerMode == AudioManager.RINGER_MODE_VIBRATE;
+            } else {
+                muted = false;
+            }
         }
-        sc.icon.setImageResource(mDemoIcon != 0 ? mDemoIcon : muted ? sc.iconMuteRes : sc.iconRes);
+        sc.icon.setImageResource(mDemoIcon != 0 ? mDemoIcon
+                : suppressor != null ? sc.iconSuppressedRes
+                : muted ? sc.iconMuteRes
+                : sc.iconRes);
     }
 
-    private void updateSliderSupressor(StreamControl sc) {
+    private void updateSliderSuppressor(StreamControl sc) {
         final ComponentName suppressor = isNotificationOrRing(sc.streamType)
-                ? mZenController.getEffectsSuppressor() : null;
+                ? mNotificationEffectsSuppressor : null;
         if (suppressor == null) {
             sc.seekbarView.setVisibility(View.VISIBLE);
             sc.suppressorView.setVisibility(View.GONE);
@@ -746,7 +770,6 @@
             sc.suppressorView.setVisibility(View.VISIBLE);
             sc.suppressorView.setText(mContext.getString(R.string.muted_by,
                     getSuppressorCaption(suppressor)));
-            sc.icon.setImageResource(sc.iconSuppressedRes);
         }
     }
 
@@ -777,7 +800,7 @@
         sc.icon.setImageDrawable(null);
         updateSliderIcon(sc, muted);
         updateSliderEnabled(sc, muted, false);
-        updateSliderSupressor(sc);
+        updateSliderSuppressor(sc);
     }
 
     private void updateSliderEnabled(final StreamControl sc, boolean muted, boolean fixedVolume) {
@@ -787,7 +810,12 @@
             // never disable touch interactions for remote playback, the muting is not tied to
             // the state of the phone.
             sc.seekbarView.setEnabled(!fixedVolume);
-        } else if (isRinger && mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) {
+        } else if (isRinger && mNotificationEffectsSuppressor != null) {
+            sc.icon.setEnabled(true);
+            sc.icon.setAlpha(1f);
+            sc.icon.setClickable(false);
+        } else if (isRinger
+                && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) {
             sc.seekbarView.setEnabled(false);
             sc.icon.setEnabled(false);
             sc.icon.setAlpha(mDisabledAlpha);
@@ -805,7 +833,7 @@
         if (isRinger && wasEnabled != sc.seekbarView.isEnabled()) {
             if (sc.seekbarView.isEnabled()) {
                 sc.group.setOnTouchListener(null);
-                sc.icon.setClickable(true);
+                sc.icon.setClickable(mHasVibrator);
             } else {
                 final View.OnTouchListener showHintOnTouch = new View.OnTouchListener() {
                     @Override
@@ -826,6 +854,16 @@
         }
     }
 
+    private void showVibrateHint() {
+        final StreamControl active = mStreamControls.get(mActiveStreamType);
+        if (active != null) {
+            mIconPulser.start(active.icon);
+            if (!hasMessages(MSG_VIBRATE)) {
+                sendEmptyMessageDelayed(MSG_VIBRATE, VIBRATE_DELAY);
+            }
+        }
+    }
+
     private static boolean isNotificationOrRing(int streamType) {
         return streamType == AudioManager.STREAM_RING
                 || streamType == AudioManager.STREAM_NOTIFICATION;
@@ -953,6 +991,19 @@
         obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection, 0).sendToTarget();
     }
 
+    public void postInternalRingerModeChanged(int mode) {
+        removeMessages(MSG_INTERNAL_RINGER_MODE_CHANGED);
+        obtainMessage(MSG_INTERNAL_RINGER_MODE_CHANGED, mode, 0).sendToTarget();
+    }
+
+    private static String flagsToString(int flags) {
+        return flags == 0 ? "0" : (flags + "=" + AudioManager.flagsToString(flags));
+    }
+
+    private static String streamToString(int stream) {
+        return AudioService.streamToString(stream);
+    }
+
     /**
      * Override this if you have other work to do when the volume changes (for
      * example, vibrating, playing a sound, etc.). Make sure to call through to
@@ -960,7 +1011,8 @@
      */
     protected void onVolumeChanged(int streamType, int flags) {
 
-        if (LOGD) Log.d(mTag, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")");
+        if (LOGD) Log.d(mTag, "onVolumeChanged(streamType: " + streamToString(streamType)
+                + ", flags: " + flagsToString(flags) + ")");
 
         if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
             synchronized (this) {
@@ -989,7 +1041,8 @@
 
     protected void onMuteChanged(int streamType, int flags) {
 
-        if (LOGD) Log.d(mTag, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")");
+        if (LOGD) Log.d(mTag, "onMuteChanged(streamType: " + streamToString(streamType)
+                + ", flags: " + flagsToString(flags) + ")");
 
         StreamControl sc = mStreamControls.get(streamType);
         if (sc != null) {
@@ -1005,8 +1058,8 @@
         mRingIsSilent = false;
 
         if (LOGD) {
-            Log.d(mTag, "onShowVolumeChanged(streamType: " + streamType
-                    + ", flags: " + flags + "), index: " + index);
+            Log.d(mTag, "onShowVolumeChanged(streamType: " + streamToString(streamType)
+                    + ", flags: " + flagsToString(flags) + "), index: " + index);
         }
 
         // get max volume for progress bar
@@ -1017,7 +1070,6 @@
         switch (streamType) {
 
             case AudioManager.STREAM_RING: {
-//                setRingerIcon();
                 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
                         mContext, RingtoneManager.TYPE_RINGTONE);
                 if (ringuri == null) {
@@ -1110,13 +1162,16 @@
                 sc.seekbarView.setMax(max);
             }
             updateSliderProgress(sc, index);
-            updateSliderEnabled(sc, isMuted(streamType),
-                    (flags & AudioManager.FLAG_FIXED_VOLUME) != 0);
-            // check for secondary-icon transition completion
-            if (isNotificationOrRing(streamType) && mSecondaryIconTransition.isRunning()) {
-                mSecondaryIconTransition.cancel();  // safe to reset
-                sc.seekbarView.setAlpha(0); sc.seekbarView.animate().alpha(1);
-                mZenPanel.setAlpha(0); mZenPanel.animate().alpha(1);
+            final boolean muted = isMuted(streamType);
+            updateSliderEnabled(sc, muted, (flags & AudioManager.FLAG_FIXED_VOLUME) != 0);
+            if (isNotificationOrRing(streamType)) {
+                // check for secondary-icon transition completion
+                if (mSecondaryIconTransition.isRunning()) {
+                    mSecondaryIconTransition.cancel();  // safe to reset
+                    sc.seekbarView.setAlpha(0); sc.seekbarView.animate().alpha(1);
+                    mZenPanel.setAlpha(0); mZenPanel.animate().alpha(1);
+                }
+                updateSliderIcon(sc, muted);
             }
         }
 
@@ -1134,15 +1189,20 @@
         // Do a little vibrate if applicable (only when going into vibrate mode)
         if ((streamType != STREAM_REMOTE_MUSIC) &&
                 ((flags & AudioManager.FLAG_VIBRATE) != 0) &&
-                mAudioManager.isStreamAffectedByRingerMode(streamType) &&
-                mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
+                isNotificationOrRing(streamType) &&
+                mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) {
             sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
         }
 
-        // Pulse the slider icon if an adjustment was suppressed due to silent mode.
+        // Pulse the zen icon if an adjustment was suppressed due to silent mode.
         if ((flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {
             showSilentHint();
         }
+
+        // Pulse the slider icon & vibrate if an adjustment down was suppressed due to vibrate mode.
+        if ((flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) {
+            showVibrateHint();
+        }
     }
 
     private void announceDialogShown() {
@@ -1186,16 +1246,17 @@
     protected void onVibrate() {
 
         // Make sure we ended up in vibrate ringer mode
-        if (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) {
+        if (mAudioManager.getRingerModeInternal() != AudioManager.RINGER_MODE_VIBRATE) {
             return;
         }
-
-        mVibrator.vibrate(VIBRATE_DURATION, VIBRATION_ATTRIBUTES);
+        if (mVibrator != null) {
+            mVibrator.vibrate(VIBRATE_DURATION, VIBRATION_ATTRIBUTES);
+        }
     }
 
     protected void onRemoteVolumeChanged(MediaController controller, int flags) {
-        if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(controller:" + controller + ", flags: " + flags
-                    + ")");
+        if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(controller:" + controller + ", flags: "
+                + flagsToString(flags) + ")");
 
         if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || isShowing()) {
             synchronized (this) {
@@ -1385,7 +1446,9 @@
                 break;
             }
 
+            case MSG_ZEN_MODE_CHANGED:
             case MSG_RINGER_MODE_CHANGED:
+            case MSG_INTERNAL_RINGER_MODE_CHANGED:
             case MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED: {
                 if (isShowing()) {
                     updateStates();
@@ -1491,10 +1554,15 @@
         public void onZenAvailableChanged(boolean available) {
             obtainMessage(MSG_ZEN_MODE_AVAILABLE_CHANGED, available ? 1 : 0, 0).sendToTarget();
         }
+
         @Override
         public void onEffectsSupressorChanged() {
-            obtainMessage(MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED,
-                    mZenController.getEffectsSuppressor()).sendToTarget();
+            mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor();
+            sendEmptyMessage(MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED);
+        }
+
+        public void onZenChanged(int zen) {
+            sendEmptyMessage(MSG_ZEN_MODE_CHANGED);
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 7102c2a..e452b22 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -182,6 +182,15 @@
         }
 
         @Override
+        public void internalRingerModeChanged(int mode) throws RemoteException {
+            mPanel.postInternalRingerModeChanged(mode);
+            final PhoneStatusBar psb = getComponent(PhoneStatusBar.class);
+            if (psb != null) {
+                psb.onInternalRingerModeChanged();
+            }
+        }
+
+        @Override
         public ZenModeController getZenController() {
             return mPanel.getZenController();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 5b37f78..b84b138 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.volume;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
@@ -38,8 +36,6 @@
 import android.util.MathUtils;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
 import android.widget.CompoundButton;
 import android.widget.CompoundButton.OnCheckedChangeListener;
 import android.widget.ImageView;
@@ -69,7 +65,6 @@
     private static final int FOREVER_CONDITION_INDEX = 0;
     private static final int TIME_CONDITION_INDEX = 1;
     private static final int FIRST_CONDITION_INDEX = 2;
-    private static final float SILENT_HINT_PULSE_SCALE = 1.1f;
     private static final long SELECT_DEFAULT_DELAY = 300;
 
     public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
@@ -78,7 +73,7 @@
     private final LayoutInflater mInflater;
     private final H mHandler = new H();
     private final Prefs mPrefs;
-    private final Interpolator mFastOutSlowInInterpolator;
+    private final IconPulser mIconPulser;
     private final int mSubheadWarningColor;
     private final int mSubheadColor;
 
@@ -110,8 +105,7 @@
         mContext = context;
         mPrefs = new Prefs();
         mInflater = LayoutInflater.from(mContext.getApplicationContext());
-        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
-                android.R.interpolator.fast_out_slow_in);
+        mIconPulser = new IconPulser(mContext);
         final Resources res = mContext.getResources();
         mSubheadWarningColor = res.getColor(R.color.system_warning_color);
         mSubheadColor = res.getColor(R.color.qs_subhead);
@@ -283,16 +277,7 @@
         if (DEBUG) Log.d(mTag, "showSilentHint");
         if (mZenButtons == null || mZenButtons.getChildCount() == 0) return;
         final View noneButton = mZenButtons.getChildAt(0);
-        if (noneButton.getScaleX() != 1) return;  // already running
-        noneButton.animate().cancel();
-        noneButton.animate().scaleX(SILENT_HINT_PULSE_SCALE).scaleY(SILENT_HINT_PULSE_SCALE)
-                .setInterpolator(mFastOutSlowInInterpolator)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        noneButton.animate().scaleX(1).scaleY(1).setListener(null);
-                    }
-                });
+        mIconPulser.start(noneButton);
     }
 
     private void handleUpdateZen(int zen) {
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 17edb53..9d4cd99 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -433,7 +433,10 @@
 
         @Override
         public int getDataBlockSize() {
-            enforceUid(Binder.getCallingUid());
+            if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                enforceUid(Binder.getCallingUid());
+            }
 
             DataInputStream inputStream;
             try {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index d60c57f..70d0e6a 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -847,7 +847,7 @@
         mRankingHelper = new RankingHelper(getContext(),
                 new RankingWorkerHandler(mRankingThread.getLooper()),
                 extractorNames);
-        mZenModeHelper = new ZenModeHelper(getContext(), mHandler);
+        mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper());
         mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
             @Override
             public void onConfigChanged() {
@@ -970,7 +970,7 @@
 
             // Grab our optional AudioService
             mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
-            mZenModeHelper.setAudioManager(mAudioManager);
+            mZenModeHelper.onSystemReady();
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             // This observer will force an update when observe is called, causing us to
             // bind to listener services.
@@ -1412,8 +1412,8 @@
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mNotificationList) {
-                    mListeners.checkServiceTokenLocked(token);
-                    mZenModeHelper.requestFromListener(interruptionFilter);
+                    final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+                    mZenModeHelper.requestFromListener(info.component, interruptionFilter);
                     updateInterruptionFilterLocked();
                 }
             } finally {
@@ -1965,7 +1965,7 @@
             final boolean convertSoundToVibration =
                        !hasCustomVibrate
                     && hasValidSound
-                    && (mAudioManager.getRingerMode()
+                    && (mAudioManager.getRingerModeInternal()
                                == AudioManager.RINGER_MODE_VIBRATE);
 
             // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.
@@ -1973,7 +1973,7 @@
                     (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
 
             if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate)
-                    && !(mAudioManager.getRingerMode()
+                    && !(mAudioManager.getRingerModeInternal()
                             == AudioManager.RINGER_MODE_SILENT)) {
                 mVibrateNotification = record;
 
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 1a3da79..6960159 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -46,15 +46,15 @@
 
     private static final int TYPE_INTERCEPTED = 1;
     private static final int TYPE_ALLOW_DISABLE = 2;
-    private static final int TYPE_SET_RINGER_MODE = 3;
-    private static final int TYPE_DOWNTIME = 4;
-    private static final int TYPE_SET_ZEN_MODE = 5;
-    private static final int TYPE_UPDATE_ZEN_MODE = 6;
-    private static final int TYPE_EXIT_CONDITION = 7;
-    private static final int TYPE_SUBSCRIBE = 8;
-    private static final int TYPE_UNSUBSCRIBE = 9;
-    private static final int TYPE_CONFIG = 10;
-    private static final int TYPE_FOLLOW_RINGER_MODE = 11;
+    private static final int TYPE_SET_RINGER_MODE_EXTERNAL = 3;
+    private static final int TYPE_SET_RINGER_MODE_INTERNAL = 4;
+    private static final int TYPE_DOWNTIME = 5;
+    private static final int TYPE_SET_ZEN_MODE = 6;
+    private static final int TYPE_UPDATE_ZEN_MODE = 7;
+    private static final int TYPE_EXIT_CONDITION = 8;
+    private static final int TYPE_SUBSCRIBE = 9;
+    private static final int TYPE_UNSUBSCRIBE = 10;
+    private static final int TYPE_CONFIG = 11;
     private static final int TYPE_NOT_INTERCEPTED = 12;
     private static final int TYPE_DISABLE_EFFECTS = 13;
 
@@ -71,8 +71,22 @@
         append(TYPE_NOT_INTERCEPTED, record.getKey() + "," + reason);
     }
 
-    public static void traceSetRingerMode(int ringerMode) {
-        append(TYPE_SET_RINGER_MODE, ringerModeToString(ringerMode));
+    public static void traceSetRingerModeExternal(int ringerModeOld, int ringerModeNew,
+            String caller, int ringerModeInternalIn, int ringerModeInternalOut) {
+        append(TYPE_SET_RINGER_MODE_EXTERNAL, caller + ",e:" +
+                ringerModeToString(ringerModeOld) + "->" +
+                ringerModeToString(ringerModeNew)  + ",i:" +
+                ringerModeToString(ringerModeInternalIn) + "->" +
+                ringerModeToString(ringerModeInternalOut));
+    }
+
+    public static void traceSetRingerModeInternal(int ringerModeOld, int ringerModeNew,
+            String caller, int ringerModeExternalIn, int ringerModeExternalOut) {
+        append(TYPE_SET_RINGER_MODE_INTERNAL, caller + ",i:" +
+                ringerModeToString(ringerModeOld) + "->" +
+                ringerModeToString(ringerModeNew)  + ",e:" +
+                ringerModeToString(ringerModeExternalIn) + "->" +
+                ringerModeToString(ringerModeExternalOut));
     }
 
     public static void traceDowntime(int downtimeMode, int day, ArraySet<Integer> days) {
@@ -103,11 +117,6 @@
         append(TYPE_CONFIG, newConfig != null ? newConfig.toString() : null);
     }
 
-    public static void traceFollowRingerMode(int ringerMode, int oldZen, int newZen) {
-        append(TYPE_FOLLOW_RINGER_MODE, ringerModeToString(ringerMode) + ", "
-                + zenModeToString(oldZen) + " -> " + zenModeToString(newZen));
-    }
-
     public static void traceDisableEffects(NotificationRecord record, String reason) {
         append(TYPE_DISABLE_EFFECTS, record.getKey() + "," + reason);
     }
@@ -120,7 +129,8 @@
         switch (type) {
             case TYPE_INTERCEPTED: return "intercepted";
             case TYPE_ALLOW_DISABLE: return "allow_disable";
-            case TYPE_SET_RINGER_MODE: return "set_ringer_mode";
+            case TYPE_SET_RINGER_MODE_EXTERNAL: return "set_ringer_mode_external";
+            case TYPE_SET_RINGER_MODE_INTERNAL: return "set_ringer_mode_internal";
             case TYPE_DOWNTIME: return "downtime";
             case TYPE_SET_ZEN_MODE: return "set_zen_mode";
             case TYPE_UPDATE_ZEN_MODE: return "update_zen_mode";
@@ -128,7 +138,6 @@
             case TYPE_SUBSCRIBE: return "subscribe";
             case TYPE_UNSUBSCRIBE: return "unsubscribe";
             case TYPE_CONFIG: return "config";
-            case TYPE_FOLLOW_RINGER_MODE: return "follow_ringer_mode";
             case TYPE_NOT_INTERCEPTED: return "not_intercepted";
             case TYPE_DISABLE_EFFECTS: return "disable_effects";
             default: return "unknown";
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 992b6aa..5ceb503 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -21,20 +21,20 @@
 
 import android.app.AppOpsManager;
 import android.app.Notification;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
+import android.media.AudioManagerInternal;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
@@ -45,6 +45,7 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.server.LocalServices;
 
 import libcore.io.IoUtils;
 
@@ -60,12 +61,12 @@
 /**
  * NotificationManagerService helper for functionality related to zen mode.
  */
-public class ZenModeHelper {
+public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
     private static final String TAG = "ZenModeHelper";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final Context mContext;
-    private final Handler mHandler;
+    private final H mHandler;
     private final SettingsObserver mSettingsObserver;
     private final AppOpsManager mAppOps;
     private final ZenModeConfig mDefaultConfig;
@@ -74,21 +75,17 @@
     private ComponentName mDefaultPhoneApp;
     private int mZenMode;
     private ZenModeConfig mConfig;
-    private AudioManager mAudioManager;
+    private AudioManagerInternal mAudioManager;
     private int mPreviousRingerMode = -1;
 
-    public ZenModeHelper(Context context, Handler handler) {
+    public ZenModeHelper(Context context, Looper looper) {
         mContext = context;
-        mHandler = handler;
+        mHandler = new H(looper);
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mDefaultConfig = readDefaultConfig(context.getResources());
         mConfig = mDefaultConfig;
         mSettingsObserver = new SettingsObserver(mHandler);
         mSettingsObserver.observe();
-
-        final IntentFilter filter = new IntentFilter();
-        filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
-        mContext.registerReceiver(mReceiver, filter);
     }
 
     public static ZenModeConfig readDefaultConfig(Resources resources) {
@@ -111,8 +108,11 @@
         mCallbacks.add(callback);
     }
 
-    public void setAudioManager(AudioManager audioManager) {
-        mAudioManager = audioManager;
+    public void onSystemReady() {
+        mAudioManager = LocalServices.getService(AudioManagerInternal.class);
+        if (mAudioManager != null) {
+            mAudioManager.setRingerModeDelegate(this);
+        }
     }
 
     public int getZenModeListenerInterruptionFilter() {
@@ -142,10 +142,10 @@
         }
     }
 
-    public void requestFromListener(int interruptionFilter) {
+    public void requestFromListener(ComponentName name, int interruptionFilter) {
         final int newZen = zenModeFromListenerInterruptionFilter(interruptionFilter, -1);
         if (newZen != -1) {
-            setZenMode(newZen, "listener");
+            setZenMode(newZen, "listener:" + (name != null ? name.flattenToShortString() : null));
         }
     }
 
@@ -214,12 +214,13 @@
     }
 
     public void updateZenMode() {
-        final int mode = Global.getInt(mContext.getContentResolver(),
+        final int oldMode = mZenMode;
+        final int newMode = Global.getInt(mContext.getContentResolver(),
                 Global.ZEN_MODE, Global.ZEN_MODE_OFF);
-        if (mode != mZenMode) {
-            ZenLog.traceUpdateZenMode(mZenMode, mode);
+        if (oldMode != newMode) {
+            ZenLog.traceUpdateZenMode(oldMode, newMode);
         }
-        mZenMode = mode;
+        mZenMode = newMode;
         final boolean zen = mZenMode != Global.ZEN_MODE_OFF;
         final String[] exceptionPackages = null; // none (for now)
 
@@ -241,29 +242,7 @@
                 muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
                 exceptionPackages);
 
-        // force ringer mode into compliance
-        if (mAudioManager != null) {
-            int ringerMode = mAudioManager.getRingerMode();
-            int forcedRingerMode = -1;
-            if (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
-                if (ringerMode != AudioManager.RINGER_MODE_SILENT) {
-                    mPreviousRingerMode = ringerMode;
-                    if (DEBUG) Slog.d(TAG, "Silencing ringer");
-                    forcedRingerMode = AudioManager.RINGER_MODE_SILENT;
-                }
-            } else {
-                if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
-                    if (DEBUG) Slog.d(TAG, "Unsilencing ringer");
-                    forcedRingerMode = mPreviousRingerMode != -1 ? mPreviousRingerMode
-                            : AudioManager.RINGER_MODE_NORMAL;
-                    mPreviousRingerMode = -1;
-                }
-            }
-            if (forcedRingerMode != -1) {
-                mAudioManager.setRingerMode(forcedRingerMode, false /*checkZen*/);
-                ZenLog.traceSetRingerMode(forcedRingerMode);
-            }
-        }
+        onZenUpdated(oldMode, newMode);
         dispatchOnZenModeChanged();
     }
 
@@ -303,25 +282,102 @@
         return true;
     }
 
-    private void handleRingerModeChanged() {
-        if (mAudioManager != null) {
-            // follow ringer mode if necessary
-            final int ringerMode = mAudioManager.getRingerMode();
-            int newZen = -1;
-            if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
-                if (mZenMode == Global.ZEN_MODE_OFF) {
-                    newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+    private void onZenUpdated(int oldZen, int newZen) {
+        if (mAudioManager == null) return;
+        if (oldZen == newZen) return;
+
+        // force the ringer mode into compliance
+        final int ringerModeInternal = mAudioManager.getRingerModeInternal();
+        int newRingerModeInternal = ringerModeInternal;
+        switch (newZen) {
+            case Global.ZEN_MODE_NO_INTERRUPTIONS:
+                if (ringerModeInternal != AudioManager.RINGER_MODE_SILENT) {
+                    mPreviousRingerMode = ringerModeInternal;
+                    newRingerModeInternal = AudioManager.RINGER_MODE_SILENT;
                 }
-            } else if ((ringerMode == AudioManager.RINGER_MODE_NORMAL
-                    || ringerMode == AudioManager.RINGER_MODE_VIBRATE)
-                    && mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
-                newZen = Global.ZEN_MODE_OFF;
-            }
-            if (newZen != -1) {
-                ZenLog.traceFollowRingerMode(ringerMode, mZenMode, newZen);
-                setZenMode(newZen, "ringerMode");
-            }
+                break;
+            case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+            case Global.ZEN_MODE_OFF:
+                if (ringerModeInternal == AudioManager.RINGER_MODE_SILENT) {
+                    newRingerModeInternal = mPreviousRingerMode != -1 ? mPreviousRingerMode
+                            : AudioManager.RINGER_MODE_NORMAL;
+                    mPreviousRingerMode = -1;
+                }
+                break;
         }
+        if (newRingerModeInternal != -1) {
+            mAudioManager.setRingerModeInternal(newRingerModeInternal, TAG);
+        }
+    }
+
+    @Override  // RingerModeDelegate
+    public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
+            int ringerModeExternal) {
+        final boolean isChange = ringerModeOld != ringerModeNew;
+
+        int ringerModeExternalOut = ringerModeNew;
+
+        int newZen = -1;
+        switch(ringerModeNew) {
+            case AudioManager.RINGER_MODE_SILENT:
+                if (isChange) {
+                    if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS) {
+                        newZen = Global.ZEN_MODE_NO_INTERRUPTIONS;
+                    }
+                }
+                break;
+            case AudioManager.RINGER_MODE_VIBRATE:
+            case AudioManager.RINGER_MODE_NORMAL:
+                if (mZenMode != Global.ZEN_MODE_OFF) {
+                    ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
+                }
+                break;
+        }
+        if (newZen != -1) {
+            mHandler.postSetZenMode(newZen, "ringerModeInternal");
+        }
+
+        if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
+            ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
+                    ringerModeExternal, ringerModeExternalOut);
+        }
+        return ringerModeExternalOut;
+    }
+
+    @Override  // RingerModeDelegate
+    public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
+            int ringerModeInternal) {
+        int ringerModeInternalOut = ringerModeNew;
+        final boolean isChange = ringerModeOld != ringerModeNew;
+        final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
+
+        int newZen = -1;
+        switch(ringerModeNew) {
+            case AudioManager.RINGER_MODE_SILENT:
+                if (isChange) {
+                    if (mZenMode == Global.ZEN_MODE_OFF) {
+                        newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+                    }
+                    ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE
+                            : AudioManager.RINGER_MODE_NORMAL;
+                } else {
+                    ringerModeInternalOut = ringerModeInternal;
+                }
+                break;
+            case AudioManager.RINGER_MODE_VIBRATE:
+            case AudioManager.RINGER_MODE_NORMAL:
+                if (mZenMode != Global.ZEN_MODE_OFF) {
+                    newZen = Global.ZEN_MODE_OFF;
+                }
+                break;
+        }
+        if (newZen != -1) {
+            mHandler.postSetZenMode(newZen, "ringerModeExternal");
+        }
+
+        ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller, ringerModeInternal,
+                ringerModeInternalOut);
+        return ringerModeInternalOut;
     }
 
     private void dispatchOnConfigChanged() {
@@ -399,6 +455,11 @@
         return true;
     }
 
+    @Override
+    public String toString() {
+        return TAG;
+    }
+
     private boolean audienceMatches(float contactAffinity) {
         switch (mConfig.allowFrom) {
             case ZenModeConfig.SOURCE_ANYONE:
@@ -413,13 +474,6 @@
         }
     }
 
-    private final Runnable mRingerModeChanged = new Runnable() {
-        @Override
-        public void run() {
-            handleRingerModeChanged();
-        }
-    };
-
     private class SettingsObserver extends ContentObserver {
         private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE);
 
@@ -445,12 +499,26 @@
         }
     }
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            mHandler.post(mRingerModeChanged);
+    private class H extends Handler {
+        private static final int MSG_SET_ZEN = 1;
+
+        private H(Looper looper) {
+            super(looper);
         }
-    };
+
+        private void postSetZenMode(int zen, String reason) {
+            obtainMessage(MSG_SET_ZEN, zen, 0, reason).sendToTarget();
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+                case MSG_SET_ZEN:
+                    setZenMode(msg.arg1, (String) msg.obj);
+                    break;
+            }
+        }
+    }
 
     public static class Callback {
         void onConfigChanged() {}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6331dfe..b0d87e9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -96,7 +96,6 @@
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.org.conscrypt.TrustedCertificateStore;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
@@ -1645,12 +1644,18 @@
         }
 
         private void manageNotification(UserHandle userHandle) {
+            final UserInfo userInfo = mUserManager.getUserInfo(userHandle.getIdentifier());
+
+            // Inactive users or managed profiles shouldn't provoke a warning
             if (!mUserManager.isUserRunning(userHandle)) {
                 return;
             }
+            if (userInfo == null || userInfo.isManagedProfile()) {
+                return;
+            }
 
+            // Call out to KeyChain to check for user-added CAs
             boolean hasCert = false;
-            final long id = Binder.clearCallingIdentity();
             try {
                 KeyChainConnection kcs = KeyChain.bindAsUser(mContext, userHandle);
                 try {
@@ -1666,8 +1671,6 @@
                 Thread.currentThread().interrupt();
             } catch (RuntimeException e) {
                 Log.e(LOG_TAG, "Could not connect to KeyChain service", e);
-            } finally {
-                Binder.restoreCallingIdentity(id);
             }
             if (!hasCert) {
                 getNotificationManager().cancelAsUser(
@@ -1675,6 +1678,7 @@
                 return;
             }
 
+            // Build and show a warning notification
             int smallIconId;
             String contentText;
             final String ownerName = getDeviceOwnerName();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4e6a8ea..b3a696e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -549,20 +549,6 @@
             reportWtf("making display ready", e);
         }
 
-        try {
-            mPackageManagerService.performBootDexOpt();
-        } catch (Throwable e) {
-            reportWtf("performing boot dexopt", e);
-        }
-
-        try {
-            ActivityManagerNative.getDefault().showBootMessage(
-                    context.getResources().getText(
-                            com.android.internal.R.string.android_upgrading_starting_apps),
-                    false);
-        } catch (RemoteException e) {
-        }
-
         if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
             if (!disableStorage &&
                 !"0".equals(SystemProperties.get("system_init.startmountservice"))) {
@@ -578,7 +564,23 @@
                     reportWtf("starting Mount Service", e);
                 }
             }
+        }
 
+        try {
+            mPackageManagerService.performBootDexOpt();
+        } catch (Throwable e) {
+            reportWtf("performing boot dexopt", e);
+        }
+
+        try {
+            ActivityManagerNative.getDefault().showBootMessage(
+                    context.getResources().getText(
+                            com.android.internal.R.string.android_upgrading_starting_apps),
+                    false);
+        } catch (RemoteException e) {
+        }
+
+        if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
             if (!disableNonCoreServices) {
                 try {
                     Slog.i(TAG,  "LockSettingsService");