Merge "Fix negative inactiveTime on creating a new user" into mnc-dev
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index cc95c0e..2fb3203 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -85,7 +85,7 @@
     private static final int GRALLOC_USAGE_HW_RENDER = 0x00000200;
     private static final int GRALLOC_USAGE_HW_VIDEO_ENCODER = 0x00010000;
 
-    public static final int MAX_DIMEN_FOR_ROUNDING = 1080; // maximum allowed width for rounding
+    public static final int MAX_DIMEN_FOR_ROUNDING = 1920; // maximum allowed width for rounding
 
     // Keep up to date with values in system/core/include/system/window.h
     public static final int NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW = 1;
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index 348b14a..4866598 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -187,8 +187,18 @@
     private final Camera.ErrorCallback mErrorCallback = new Camera.ErrorCallback() {
         @Override
         public void onError(int i, Camera camera) {
-            Log.e(TAG, "Received error " + i + " from the Camera1 ErrorCallback");
-            mDeviceState.setError(CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+            switch(i) {
+                case Camera.CAMERA_ERROR_EVICTED: {
+                    flush();
+                    mDeviceState.setError(
+                            CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DISCONNECTED);
+                } break;
+                default:  {
+                    Log.e(TAG, "Received error " + i + " from the Camera1 ErrorCallback");
+                    mDeviceState.setError(
+                            CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+                } break;
+            }
         }
     };
 
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index f76192e..d165240 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3541,8 +3541,15 @@
                 }
                 printmAh(pw, bs.totalPowerMah);
 
-                if (bs.drainType == BatterySipper.DrainType.APP) {
+                if (bs.usagePowerMah != bs.totalPowerMah) {
+                    // If the usage (generic power) isn't the whole amount, we list out
+                    // what components are involved in the calculation.
+
                     pw.print(" (");
+                    if (bs.usagePowerMah != 0) {
+                        pw.print(" usage=");
+                        printmAh(pw, bs.usagePowerMah);
+                    }
                     if (bs.cpuPowerMah != 0) {
                         pw.print(" cpu=");
                         printmAh(pw, bs.cpuPowerMah);
diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java
index 5bc45d5..397d87e 100644
--- a/core/java/android/os/storage/DiskInfo.java
+++ b/core/java/android/os/storage/DiskInfo.java
@@ -40,6 +40,8 @@
             "android.os.storage.action.DISK_SCANNED";
     public static final String EXTRA_DISK_ID =
             "android.os.storage.extra.DISK_ID";
+    public static final String EXTRA_VOLUME_COUNT =
+            "android.os.storage.extra.VOLUME_COUNT";
 
     public static final int FLAG_ADOPTABLE = 1 << 0;
     public static final int FLAG_DEFAULT_PRIMARY = 1 << 1;
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 372725f..8d11527 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -55,6 +55,8 @@
             "android.os.storage.action.VOLUME_STATE_CHANGED";
     public static final String EXTRA_VOLUME_ID =
             "android.os.storage.extra.VOLUME_ID";
+    public static final String EXTRA_VOLUME_STATE =
+            "android.os.storage.extra.VOLUME_STATE";
 
     /** Stub volume representing internal private storage */
     public static final String ID_PRIVATE_INTERNAL = "private";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f2d3e71..e335f6d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5551,13 +5551,6 @@
         public static final String SLEEP_TIMEOUT = "sleep_timeout";
 
         /**
-         * Duration in milliseconds that an app should be inactive before it is considered idle.
-         * <p/>Type: Long
-         * @hide
-         */
-        public static final String APP_IDLE_DURATION = "app_idle_duration";
-
-        /**
          * Controls whether double tap to wake is enabled.
          * @hide
          */
@@ -7117,6 +7110,28 @@
         public static final String DEVICE_IDLE_CONSTANTS = "device_idle_constants";
 
         /**
+         * App standby (app idle) specific settings.
+         * This is encoded as a key=value list, separated by commas. Ex:
+         *
+         * "idle_duration=5000,parole_interval=4500"
+         *
+         * The following keys are supported:
+         *
+         * <pre>
+         * idle_duration        (long)
+         * wallclock_threshold  (long)
+         * parole_interval      (long)
+         * parole_duration      (long)
+         * </pre>
+         *
+         * <p>
+         * Type: string
+         * @hide
+         * @see com.android.server.usage.UsageStatsService.SettingsObserver
+         */
+        public static final String APP_IDLE_CONSTANTS = "app_idle_constants";
+
+        /**
          * Get the key that retrieves a bluetooth headset's priority.
          * @hide
          */
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index 7ae7d0f..05cfd81 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -47,7 +47,6 @@
 import android.graphics.drawable.Drawable;
 import android.util.TypedValue;
 import android.view.ActionMode;
-import android.view.ActionMode.Callback;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -106,6 +105,10 @@
 
     private static final int INVALID_POSITION = -1;
 
+    // The fade duration for toolbar and action bar when entering/exiting action mode.
+    private static final long FADE_OUT_DURATION_MS = 100;
+    private static final long FADE_IN_DURATION_MS = 200;
+
     private int mContextDisplayMode;
     private boolean mHasEmbeddedTabs;
 
@@ -866,8 +869,21 @@
             hideForActionMode();
         }
 
-        mDecorToolbar.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE);
-        mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE);
+        Animator fadeIn, fadeOut;
+        if (toActionMode) {
+            fadeOut = mDecorToolbar.setupAnimatorToVisibility(View.GONE,
+                    FADE_OUT_DURATION_MS);
+            fadeIn = mContextView.setupAnimatorToVisibility(View.VISIBLE,
+                    FADE_IN_DURATION_MS);
+        } else {
+            fadeIn = mDecorToolbar.setupAnimatorToVisibility(View.VISIBLE,
+                    FADE_IN_DURATION_MS);
+            fadeOut = mContextView.setupAnimatorToVisibility(View.GONE,
+                    FADE_OUT_DURATION_MS);
+        }
+        AnimatorSet set = new AnimatorSet();
+        set.playSequentially(fadeOut, fadeIn);
+        set.start();
         // mTabScrollView's visibility is not affected by action mode.
     }
 
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index 4290e22..6a85afb 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -587,6 +587,7 @@
             bs.add(wbs);
         }
         bs.computeMobilemspp();
+        bs.sumPower();
     }
 
     private void addIdleUsage() {
@@ -612,9 +613,8 @@
     private void addWiFiUsage() {
         BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0);
         mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtime, mRawUptime, mStatsType);
-        bs.sumPower();
-        if (bs.totalPowerMah > 0 || !mWifiSippers.isEmpty()) {
-            aggregateSippers(bs, mWifiSippers, "WIFI");
+        aggregateSippers(bs, mWifiSippers, "WIFI");
+        if (bs.totalPowerMah > 0) {
             mUsageList.add(bs);
         }
     }
@@ -627,8 +627,8 @@
         BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0);
         mBluetoothPowerCalculator.calculateRemaining(bs, mStats, mRawRealtime, mRawUptime,
                 mStatsType);
-        if (bs.sumPower() > 0) {
-            aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
+        aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
+        if (bs.totalPowerMah > 0) {
             mUsageList.add(bs);
         }
     }
@@ -639,7 +639,6 @@
             BatterySipper bs = new BatterySipper(DrainType.USER, null, 0);
             bs.userId = userId;
             aggregateSippers(bs, mUserSippers.valueAt(i), "User");
-            bs.sumPower();
             mUsageList.add(bs);
         }
     }
diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java
index 850ea23..35eeca7 100644
--- a/core/java/com/android/internal/widget/AbsActionBarView.java
+++ b/core/java/com/android/internal/widget/AbsActionBarView.java
@@ -136,10 +136,11 @@
         return getVisibility();
     }
 
-    public void animateToVisibility(int visibility) {
+    public Animator setupAnimatorToVisibility(int visibility, long duration) {
         if (mVisibilityAnim != null) {
             mVisibilityAnim.cancel();
         }
+
         if (visibility == VISIBLE) {
             if (getVisibility() != VISIBLE) {
                 setAlpha(0);
@@ -147,38 +148,43 @@
                     mMenuView.setAlpha(0);
                 }
             }
-            ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 1);
-            anim.setDuration(FADE_DURATION);
+            ObjectAnimator anim = ObjectAnimator.ofFloat(this, View.ALPHA, 1);
+            anim.setDuration(duration);
             anim.setInterpolator(sAlphaInterpolator);
             if (mSplitView != null && mMenuView != null) {
                 AnimatorSet set = new AnimatorSet();
-                ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, "alpha", 1);
-                splitAnim.setDuration(FADE_DURATION);
+                ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, View.ALPHA, 1);
+                splitAnim.setDuration(duration);
                 set.addListener(mVisAnimListener.withFinalVisibility(visibility));
                 set.play(anim).with(splitAnim);
-                set.start();
+                return set;
             } else {
                 anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
-                anim.start();
+                return anim;
             }
         } else {
-            ObjectAnimator anim = ObjectAnimator.ofFloat(this, "alpha", 0);
-            anim.setDuration(FADE_DURATION);
+            ObjectAnimator anim = ObjectAnimator.ofFloat(this, View.ALPHA, 0);
+            anim.setDuration(duration);
             anim.setInterpolator(sAlphaInterpolator);
             if (mSplitView != null && mMenuView != null) {
                 AnimatorSet set = new AnimatorSet();
-                ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, "alpha", 0);
-                splitAnim.setDuration(FADE_DURATION);
+                ObjectAnimator splitAnim = ObjectAnimator.ofFloat(mMenuView, View.ALPHA, 0);
+                splitAnim.setDuration(duration);
                 set.addListener(mVisAnimListener.withFinalVisibility(visibility));
                 set.play(anim).with(splitAnim);
-                set.start();
+                return set;
             } else {
                 anim.addListener(mVisAnimListener.withFinalVisibility(visibility));
-                anim.start();
+                return anim;
             }
         }
     }
 
+    public void animateToVisibility(int visibility) {
+        Animator anim = setupAnimatorToVisibility(visibility, FADE_DURATION);
+        anim.start();
+    }
+
     @Override
     public void setVisibility(int visibility) {
         if (visibility != getVisibility()) {
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index c5d3290..693b194 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -21,10 +21,6 @@
 import android.widget.ActionMenuView;
 import com.android.internal.view.menu.MenuBuilder;
 
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
@@ -35,14 +31,13 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.DecelerateInterpolator;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 /**
  * @hide
  */
-public class ActionBarContextView extends AbsActionBarView implements AnimatorListener {
+public class ActionBarContextView extends AbsActionBarView {
     private static final String TAG = "ActionBarContextView";
 
     private CharSequence mTitle;
@@ -59,14 +54,6 @@
     private boolean mTitleOptional;
     private int mCloseItemLayout;
 
-    private Animator mCurrentAnimation;
-    private boolean mAnimateInOnLayout;
-    private int mAnimationMode;
-
-    private static final int ANIMATE_IDLE = 0;
-    private static final int ANIMATE_IN = 1;
-    private static final int ANIMATE_OUT = 2;
-    
     public ActionBarContextView(Context context) {
         this(context, null);
     }
@@ -255,43 +242,23 @@
             mMenuView.setBackgroundDrawable(mSplitBackground);
             mSplitView.addView(mMenuView, layoutParams);
         }
-
-        mAnimateInOnLayout = true;
     }
 
     public void closeMode() {
-        if (mAnimationMode == ANIMATE_OUT) {
-            // Called again during close; just finish what we were doing.
-            return;
-        }
         if (mClose == null) {
             killMode();
             return;
         }
 
-        finishAnimation();
-        mAnimationMode = ANIMATE_OUT;
-        mCurrentAnimation = makeOutAnimation();
-        mCurrentAnimation.start();
-    }
-
-    private void finishAnimation() {
-        final Animator a = mCurrentAnimation;
-        if (a != null) {
-            mCurrentAnimation = null;
-            a.end();
-        }
     }
 
     public void killMode() {
-        finishAnimation();
         removeAllViews();
         if (mSplitView != null) {
             mSplitView.removeView(mMenuView);
         }
         mCustomView = null;
         mMenuView = null;
-        mAnimateInOnLayout = false;
     }
 
     @Override
@@ -343,7 +310,7 @@
             throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
                     "with android:layout_height=\"wrap_content\"");
         }
-        
+
         final int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
 
         int maxHeight = mContentHeight > 0 ?
@@ -353,7 +320,7 @@
         int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight();
         final int height = maxHeight - verticalPadding;
         final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
-        
+
         if (mClose != null) {
             availableWidth = measureChildView(mClose, availableWidth, childSpecHeight, 0);
             MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams();
@@ -411,66 +378,13 @@
         }
     }
 
-    private Animator makeInAnimation() {
-        mClose.setTranslationX(-mClose.getWidth() -
-                ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin);
-        ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX", 0);
-        buttonAnimator.setDuration(200);
-        buttonAnimator.addListener(this);
-        buttonAnimator.setInterpolator(new DecelerateInterpolator());
-
-        AnimatorSet set = new AnimatorSet();
-        AnimatorSet.Builder b = set.play(buttonAnimator);
-
-        if (mMenuView != null) {
-            final int count = mMenuView.getChildCount();
-            if (count > 0) {
-                for (int i = count - 1, j = 0; i >= 0; i--, j++) {
-                    View child = mMenuView.getChildAt(i);
-                    child.setScaleY(0);
-                    ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0, 1);
-                    a.setDuration(300);
-                    b.with(a);
-                }
-            }
-        }
-
-        return set;
-    }
-
-    private Animator makeOutAnimation() {
-        ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX",
-                -mClose.getWidth() - ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin);
-        buttonAnimator.setDuration(200);
-        buttonAnimator.addListener(this);
-        buttonAnimator.setInterpolator(new DecelerateInterpolator());
-
-        AnimatorSet set = new AnimatorSet();
-        AnimatorSet.Builder b = set.play(buttonAnimator);
-
-        if (mMenuView != null) {
-            final int count = mMenuView.getChildCount();
-            if (count > 0) {
-                for (int i = 0; i < 0; i++) {
-                    View child = mMenuView.getChildAt(i);
-                    child.setScaleY(0);
-                    ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0);
-                    a.setDuration(300);
-                    b.with(a);
-                }
-            }
-        }
-
-        return set;
-    }
-
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         final boolean isLayoutRtl = isLayoutRtl();
         int x = isLayoutRtl ? r - l - getPaddingRight() : getPaddingLeft();
         final int y = getPaddingTop();
         final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
-        
+
         if (mClose != null && mClose.getVisibility() != GONE) {
             MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams();
             final int startMargin = (isLayoutRtl ? lp.rightMargin : lp.leftMargin);
@@ -479,12 +393,6 @@
             x += positionChild(mClose, x, y, contentHeight, isLayoutRtl);
             x = next(x, endMargin, isLayoutRtl);
 
-            if (mAnimateInOnLayout) {
-                mAnimationMode = ANIMATE_IN;
-                mCurrentAnimation = makeInAnimation();
-                mCurrentAnimation.start();
-                mAnimateInOnLayout = false;
-            }
         }
 
         if (mTitleLayout != null && mCustomView == null && mTitleLayout.getVisibility() != GONE) {
@@ -503,26 +411,6 @@
     }
 
     @Override
-    public void onAnimationStart(Animator animation) {
-    }
-
-    @Override
-    public void onAnimationEnd(Animator animation) {
-        if (mAnimationMode == ANIMATE_OUT) {
-            killMode();
-        }
-        mAnimationMode = ANIMATE_IDLE;
-    }
-
-    @Override
-    public void onAnimationCancel(Animator animation) {
-    }
-
-    @Override
-    public void onAnimationRepeat(Animator animation) {
-    }
-
-    @Override
     public boolean shouldDelayChildPressedState() {
         return false;
     }
diff --git a/core/java/com/android/internal/widget/DecorToolbar.java b/core/java/com/android/internal/widget/DecorToolbar.java
index fb413b5..fe70d7b 100644
--- a/core/java/com/android/internal/widget/DecorToolbar.java
+++ b/core/java/com/android/internal/widget/DecorToolbar.java
@@ -17,6 +17,7 @@
 
 package com.android.internal.widget;
 
+import android.animation.Animator;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.os.Parcelable;
@@ -87,6 +88,7 @@
     void setCustomView(View view);
     View getCustomView();
     void animateToVisibility(int visibility);
+    Animator setupAnimatorToVisibility(int visibility, long duration);
     void setNavigationIcon(Drawable icon);
     void setNavigationIcon(int resId);
     void setNavigationContentDescription(CharSequence description);
diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
index 54df87b..32aae72 100644
--- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
+++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
@@ -19,6 +19,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.app.ActionBar;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -59,6 +60,8 @@
 
     private static final int AFFECTS_LOGO_MASK =
             ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO;
+    // Default fade duration for fading in/out tool bar.
+    private static final long DEFAULT_FADE_DURATION_MS = 200;
 
     private Toolbar mToolbar;
 
@@ -571,9 +574,19 @@
 
     @Override
     public void animateToVisibility(int visibility) {
+        Animator anim = setupAnimatorToVisibility(visibility, DEFAULT_FADE_DURATION_MS);
+        if (anim != null) {
+            anim.start();
+        }
+    }
+
+    @Override
+    public Animator setupAnimatorToVisibility(int visibility, long duration) {
+
         if (visibility == View.GONE) {
-            mToolbar.animate().alpha(0)
-                    .setListener(new AnimatorListenerAdapter() {
+            ObjectAnimator anim = ObjectAnimator.ofFloat(mToolbar, View.ALPHA, 1, 0);
+            anim.setDuration(duration);
+            anim.addListener(new AnimatorListenerAdapter() {
                         private boolean mCanceled = false;
                         @Override
                         public void onAnimationEnd(Animator animation) {
@@ -587,15 +600,19 @@
                             mCanceled = true;
                         }
                     });
+            return anim;
         } else if (visibility == View.VISIBLE) {
-            mToolbar.animate().alpha(1)
-                    .setListener(new AnimatorListenerAdapter() {
+            ObjectAnimator anim = ObjectAnimator.ofFloat(mToolbar, View.ALPHA, 0, 1);
+            anim.setDuration(duration);
+            anim.addListener(new AnimatorListenerAdapter() {
                         @Override
                         public void onAnimationStart(Animator animation) {
                             mToolbar.setVisibility(View.VISIBLE);
                         }
                     });
+            return anim;
         }
+        return null;
     }
 
     @Override
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index a13dc85..97df978e 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -58,6 +58,7 @@
                 android:layout_marginStart="4dp"
                 android:scaleType="fitCenter"
                 android:visibility="gone"
+                android:contentDescription="@string/notification_work_profile_content_description"
                 />
         </LinearLayout>
         <include
diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml
index b677a98..7ae29fb 100644
--- a/core/res/res/layout/notification_template_material_big_text.xml
+++ b/core/res/res/layout/notification_template_material_big_text.xml
@@ -60,6 +60,7 @@
                 android:layout_marginStart="4dp"
                 android:scaleType="fitCenter"
                 android:visibility="gone"
+                android:contentDescription="@string/notification_work_profile_content_description"
                 />
         </LinearLayout>
         <ImageView
diff --git a/core/res/res/layout/notification_template_material_inbox.xml b/core/res/res/layout/notification_template_material_inbox.xml
index c109225..950ae40 100644
--- a/core/res/res/layout/notification_template_material_inbox.xml
+++ b/core/res/res/layout/notification_template_material_inbox.xml
@@ -62,6 +62,7 @@
                 android:layout_marginStart="4dp"
                 android:scaleType="fitCenter"
                 android:visibility="gone"
+                android:contentDescription="@string/notification_work_profile_content_description"
                 />
         </LinearLayout>
         <TextView android:id="@+id/inbox_text1"
diff --git a/core/res/res/layout/notification_template_part_line2.xml b/core/res/res/layout/notification_template_part_line2.xml
index aeef3ab..db43271 100644
--- a/core/res/res/layout/notification_template_part_line2.xml
+++ b/core/res/res/layout/notification_template_part_line2.xml
@@ -43,6 +43,7 @@
             android:layout_marginStart="4dp"
             android:scaleType="fitCenter"
             android:visibility="gone"
+            android:contentDescription="@string/notification_work_profile_content_description"
             />
     </LinearLayout>
     <ViewStub
diff --git a/core/res/res/layout/notification_template_part_line3.xml b/core/res/res/layout/notification_template_part_line3.xml
index 6c043a0..da3c5c5 100644
--- a/core/res/res/layout/notification_template_part_line3.xml
+++ b/core/res/res/layout/notification_template_part_line3.xml
@@ -51,5 +51,6 @@
         android:layout_marginStart="4dp"
         android:scaleType="fitCenter"
         android:visibility="gone"
+        android:contentDescription="@string/notification_work_profile_content_description"
         />
 </LinearLayout>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a6b7e35..915a445 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4168,6 +4168,9 @@
     <string name="stk_cc_ss_to_ussd">SS request is modified to USSD request.</string>
     <string name="stk_cc_ss_to_ss">SS request is modified to new SS request.</string>
 
+    <!-- Content description of the work profile icon in the notification. -->
+    <string name="notification_work_profile_content_description">Work profile</string>
+
     <!-- User visible name for USB MIDI Peripheral port -->
     <string name="usb_midi_peripheral_name">Android USB Peripheral Port</string>
     <!-- Manufacturer name for USB MIDI Peripheral port -->
diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h
index 9354e94..b35db28 100644
--- a/libs/hwui/CanvasState.h
+++ b/libs/hwui/CanvasState.h
@@ -155,8 +155,9 @@
     inline bool currentlyIgnored() const { return currentSnapshot()->isIgnored(); }
     int getViewportWidth() const { return currentSnapshot()->getViewportWidth(); }
     int getViewportHeight() const { return currentSnapshot()->getViewportHeight(); }
-    int getWidth() { return mWidth; }
-    int getHeight() { return mHeight; }
+    int getWidth() const { return mWidth; }
+    int getHeight() const { return mHeight; }
+    bool clipIsSimple() const { return currentSnapshot()->clipIsSimple(); }
 
     inline const Snapshot* currentSnapshot() const {
         return mSnapshot != nullptr ? mSnapshot.get() : mFirstSnapshot.get();
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index b077a85..f05857c 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -494,7 +494,9 @@
     // complex clip has a complex set of expectations on the renderer state - for now, avoid taking
     // the merge path in those cases
     deferInfo.mergeable &= !recordingComplexClip();
-    deferInfo.opaqueOverBounds &= !recordingComplexClip() && mSaveStack.isEmpty();
+    deferInfo.opaqueOverBounds &= !recordingComplexClip()
+            && mSaveStack.isEmpty()
+            && !state->mRoundRectClipState;
 
     if (CC_LIKELY(mAvoidOverdraw) && mBatches.size() &&
             state->mClipSideFlags != kClipSide_ConservativeFull &&
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index 900a621..843c412 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -203,10 +203,10 @@
 
 void DisplayListCanvas::drawRenderNode(RenderNode* renderNode) {
     LOG_ALWAYS_FATAL_IF(!renderNode, "missing rendernode");
-
-    // dirty is an out parameter and should not be recorded,
-    // it matters only when replaying the display list
-    DrawRenderNodeOp* op = new (alloc()) DrawRenderNodeOp(renderNode, *mState.currentTransform());
+    DrawRenderNodeOp* op = new (alloc()) DrawRenderNodeOp(
+            renderNode,
+            *mState.currentTransform(),
+            mState.clipIsSimple());
     addRenderNodeOp(op);
 }
 
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index e9d6ebc..fb2852a 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1398,9 +1398,10 @@
     friend class RenderNode; // grant RenderNode access to info of child
     friend class DisplayListData; // grant DisplayListData access to info of child
 public:
-    DrawRenderNodeOp(RenderNode* renderNode, const mat4& transformFromParent)
+    DrawRenderNodeOp(RenderNode* renderNode, const mat4& transformFromParent, bool clipIsSimple)
             : DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), nullptr)
             , mRenderNode(renderNode)
+            , mRecordedWithPotentialStencilClip(!clipIsSimple || !transformFromParent.isSimple())
             , mTransformFromParent(transformFromParent)
             , mSkipInOrderDraw(false) {}
 
@@ -1436,6 +1437,20 @@
 private:
     RenderNode* mRenderNode;
 
+    /**
+     * This RenderNode was drawn into a DisplayList with the canvas in a state that will likely
+     * require rendering with stencil clipping. Either:
+     *
+     * 1) A path clip or rotated rect clip was in effect on the canvas at record time
+     * 2) The RenderNode was recorded with a non-simple canvas transform (e.g. rotation)
+     *
+     * Note: even if this is false, non-rect clipping may still be applied applied either due to
+     * property-driven rotation (either in this RenderNode, or any ancestor), or record time
+     * clipping in an ancestor. These are handled in RenderNode::prepareTreeImpl since they are
+     * dynamic (relative to a static DisplayList of a parent), and don't affect this flag.
+     */
+    bool mRecordedWithPotentialStencilClip;
+
     ///////////////////////////
     // Properties below are used by RenderNode::computeOrderingImpl() and issueOperations()
     ///////////////////////////
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index b7cdaa2..288fed3 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -29,8 +29,9 @@
 #include <GLES2/gl2.h>
 #include <SkPaint.h>
 
-namespace android {
-namespace uirenderer {
+#define DEBUG_GLOP_BUILDER 0
+
+#if DEBUG_GLOP_BUILDER
 
 #define TRIGGER_STAGE(stageFlag) \
     LOG_ALWAYS_FATAL_IF((stageFlag) & mStageFlags, "Stage %d cannot be run twice", (stageFlag)); \
@@ -40,6 +41,16 @@
     LOG_ALWAYS_FATAL_IF((mStageFlags & (requiredFlags)) != (requiredFlags), \
             "not prepared for current stage")
 
+#else
+
+#define TRIGGER_STAGE(stageFlag) ((void)0)
+#define REQUIRE_STAGES(requiredFlags) ((void)0)
+
+#endif
+
+namespace android {
+namespace uirenderer {
+
 static void setUnitQuadTextureCoords(Rect uvs, TextureVertex* quadVertex) {
     quadVertex[0] = {0, 0, uvs.left, uvs.top};
     quadVertex[1] = {1, 0, uvs.right, uvs.top};
@@ -301,7 +312,7 @@
 GlopBuilder& GlopBuilder::setFillTexturePaint(Texture& texture,
         const int textureFillFlags, const SkPaint* paint, float alphaScale) {
     TRIGGER_STAGE(kFillStage);
-    REQUIRE_STAGES(kMeshStage);
+    REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
 
     GLenum filter = (textureFillFlags & TextureFillFlags::ForceFilter)
             ? GL_LINEAR : PaintUtils::getFilter(paint);
@@ -345,7 +356,7 @@
 
 GlopBuilder& GlopBuilder::setFillPaint(const SkPaint& paint, float alphaScale) {
     TRIGGER_STAGE(kFillStage);
-    REQUIRE_STAGES(kMeshStage);
+    REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
 
     mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
 
@@ -359,7 +370,7 @@
 GlopBuilder& GlopBuilder::setFillPathTexturePaint(PathTexture& texture,
         const SkPaint& paint, float alphaScale) {
     TRIGGER_STAGE(kFillStage);
-    REQUIRE_STAGES(kMeshStage);
+    REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
 
     //specify invalid filter/clamp, since these are always static for PathTextures
     mOutGlop->fill.texture = { &texture, GL_TEXTURE_2D, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
@@ -376,7 +387,7 @@
 GlopBuilder& GlopBuilder::setFillShadowTexturePaint(ShadowTexture& texture, int shadowColor,
         const SkPaint& paint, float alphaScale) {
     TRIGGER_STAGE(kFillStage);
-    REQUIRE_STAGES(kMeshStage);
+    REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
 
     //specify invalid filter/clamp, since these are always static for ShadowTextures
     mOutGlop->fill.texture = { &texture, GL_TEXTURE_2D, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
@@ -399,7 +410,7 @@
 
 GlopBuilder& GlopBuilder::setFillBlack() {
     TRIGGER_STAGE(kFillStage);
-    REQUIRE_STAGES(kMeshStage);
+    REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
 
     mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
     setFill(SK_ColorBLACK, 1.0f, SkXfermode::kSrcOver_Mode, Blend::ModeOrderSwap::NoSwap,
@@ -409,7 +420,7 @@
 
 GlopBuilder& GlopBuilder::setFillClear() {
     TRIGGER_STAGE(kFillStage);
-    REQUIRE_STAGES(kMeshStage);
+    REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
 
     mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
     setFill(SK_ColorBLACK, 1.0f, SkXfermode::kClear_Mode, Blend::ModeOrderSwap::NoSwap,
@@ -420,7 +431,7 @@
 GlopBuilder& GlopBuilder::setFillLayer(Texture& texture, const SkColorFilter* colorFilter,
         float alpha, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage) {
     TRIGGER_STAGE(kFillStage);
-    REQUIRE_STAGES(kMeshStage);
+    REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
 
     mOutGlop->fill.texture = { &texture,
             GL_TEXTURE_2D, GL_LINEAR, GL_CLAMP_TO_EDGE, nullptr };
@@ -434,7 +445,7 @@
 
 GlopBuilder& GlopBuilder::setFillTextureLayer(Layer& layer, float alpha) {
     TRIGGER_STAGE(kFillStage);
-    REQUIRE_STAGES(kMeshStage);
+    REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
 
     mOutGlop->fill.texture = { &(layer.getTexture()),
             layer.getRenderTarget(), GL_LINEAR, GL_CLAMP_TO_EDGE, &layer.getTexTransform() };
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 5769376..433e178 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -847,11 +847,11 @@
             && layer->getHeight() == (uint32_t) rect.getHeight();
     Glop glop;
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshTexturedUvQuad(nullptr, Rect(0, 1, 1, 0)) // TODO: simplify with VBO
             .setFillTextureLayer(*layer, getLayerAlpha(layer))
             .setTransform(*currentSnapshot(), TransformFlags::None)
             .setModelViewMapUnitToRectOptionalSnap(tryToSnap, rect)
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     renderGlop(glop);
 }
@@ -859,12 +859,12 @@
 void OpenGLRenderer::composeLayerRectSwapped(Layer* layer, const Rect& rect) {
     Glop glop;
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshTexturedUvQuad(nullptr, layer->texCoords)
             .setFillLayer(layer->getTexture(), layer->getColorFilter(),
                     getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::Swap)
             .setTransform(*currentSnapshot(), TransformFlags::MeshIgnoresCanvasTransform)
             .setModelViewMapUnitToRect(rect)
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     renderGlop(glop);
 }
@@ -880,11 +880,11 @@
                 && layer->getHeight() == static_cast<uint32_t>(rect.getHeight());
         Glop glop;
         GlopBuilder(mRenderState, mCaches, &glop)
+                .setRoundRectClipState(currentSnapshot()->roundRectClipState)
                 .setMeshTexturedUvQuad(nullptr, layer->texCoords)
                 .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::NoSwap)
                 .setTransform(*currentSnapshot(), TransformFlags::None)
                 .setModelViewMapUnitToRectOptionalSnap(tryToSnap, rect)
-                .setRoundRectClipState(currentSnapshot()->roundRectClipState)
                 .build();
         renderGlop(glop);
     }
@@ -1021,11 +1021,11 @@
     Rect modelRect = Rect(rect.getWidth(), rect.getHeight());
     Glop glop;
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshTexturedIndexedQuads(&quadVertices[0], count * 6)
             .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::NoSwap)
             .setTransform(*currentSnapshot(),  TransformFlags::None)
             .setModelViewOffsetRectSnap(rect.left, rect.top, modelRect)
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate, renderGlop(glop));
 
@@ -1140,11 +1140,11 @@
         const int transformFlags = TransformFlags::MeshIgnoresCanvasTransform;
         Glop glop;
         GlopBuilder(mRenderState, mCaches, &glop)
+                .setRoundRectClipState(nullptr) // clear ignores clip state
                 .setMeshIndexedQuads(&mesh[0], quadCount)
                 .setFillClear()
                 .setTransform(*currentSnapshot(), transformFlags)
                 .setModelViewOffsetRect(0, 0, Rect(currentSnapshot()->getClipRect()))
-                .setRoundRectClipState(currentSnapshot()->roundRectClipState)
                 .build();
         renderGlop(glop, false);
 
@@ -1330,11 +1330,11 @@
     Glop glop;
     Vertex* vertices = &rectangleVertices[0];
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshIndexedQuads(vertices, rectangleVertices.size() / 4)
             .setFillBlack()
             .setTransform(*currentSnapshot(), transformFlags)
             .setModelViewOffsetRect(0, 0, scissorBox)
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     renderGlop(glop);
 }
@@ -1534,11 +1534,11 @@
     const int transformFlags = TransformFlags::MeshIgnoresCanvasTransform;
     Glop glop;
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshTexturedMesh(vertices, bitmapCount * 6)
             .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha)
             .setTransform(*currentSnapshot(), transformFlags)
             .setModelViewOffsetRectOptionalSnap(snap, x, y, Rect(0, 0, bounds.getWidth(), bounds.getHeight()))
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     renderGlop(glop);
 }
@@ -1557,11 +1557,11 @@
             ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
     Glop glop;
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshTexturedUnitQuad(texture->uvMapper)
             .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha)
             .setTransform(*currentSnapshot(),  TransformFlags::None)
             .setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height))
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     renderGlop(glop);
 }
@@ -1647,11 +1647,11 @@
     const int textureFillFlags = TextureFillFlags::None;
     Glop glop;
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshColoredTexturedMesh(mesh.get(), elementCount)
             .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha)
             .setTransform(*currentSnapshot(),  TransformFlags::None)
             .setModelViewOffsetRect(0, 0, Rect(left, top, right, bottom))
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     renderGlop(glop);
 }
@@ -1676,11 +1676,11 @@
             && MathUtils::areEqual(src.getHeight(), dst.getHeight());
     Glop glop;
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshTexturedUvQuad(texture->uvMapper, uv)
             .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha)
             .setTransform(*currentSnapshot(),  TransformFlags::None)
             .setModelViewMapUnitToRectOptionalSnap(tryToSnap, dst)
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     renderGlop(glop);
 }
@@ -1702,11 +1702,11 @@
     }
     Glop glop;
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshPatchQuads(*mesh)
             .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha)
             .setTransform(*currentSnapshot(),  TransformFlags::None)
             .setModelViewOffsetRectSnap(left, top, Rect(0, 0, right - left, bottom - top)) // TODO: get minimal bounds from patch
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     renderGlop(glop);
 }
@@ -1732,11 +1732,11 @@
     }
     Glop glop;
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshTexturedIndexedQuads(vertices, elementCount)
             .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha)
             .setTransform(*currentSnapshot(), transformFlags)
             .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0))
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     renderGlop(glop);
 }
@@ -1753,11 +1753,11 @@
     const int transformFlags = TransformFlags::OffsetByFudgeFactor;
     Glop glop;
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshVertexBuffer(vertexBuffer, shadowInterp)
             .setFillPaint(*paint, currentSnapshot()->alpha)
             .setTransform(*currentSnapshot(), transformFlags)
             .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds())
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     renderGlop(glop);
 }
@@ -2039,11 +2039,11 @@
 
     Glop glop;
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshTexturedUnitQuad(nullptr)
             .setFillShadowTexturePaint(*texture, textShadow.color, *paint, currentSnapshot()->alpha)
             .setTransform(*currentSnapshot(),  TransformFlags::None)
             .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     renderGlop(glop);
 }
@@ -2364,11 +2364,11 @@
         } else if (layer->mesh) {
             Glop glop;
             GlopBuilder(mRenderState, mCaches, &glop)
+                    .setRoundRectClipState(currentSnapshot()->roundRectClipState)
                     .setMeshTexturedIndexedQuads(layer->mesh, layer->meshElementCount)
                     .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::NoSwap)
                     .setTransform(*currentSnapshot(),  TransformFlags::None)
                     .setModelViewOffsetRectSnap(x, y, Rect(0, 0, layer->layer.getWidth(), layer->layer.getHeight()))
-                    .setRoundRectClipState(currentSnapshot()->roundRectClipState)
                     .build();
             DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate, renderGlop(glop));
 #if DEBUG_LAYERS_AS_REGIONS
@@ -2422,11 +2422,11 @@
 
     Glop glop;
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshTexturedUnitQuad(nullptr)
             .setFillPathTexturePaint(*texture, *paint, currentSnapshot()->alpha)
             .setTransform(*currentSnapshot(),  TransformFlags::None)
             .setModelViewMapUnitToRect(Rect(x, y, x + texture->width, y + texture->height))
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     renderGlop(glop);
 }
@@ -2560,11 +2560,11 @@
             ? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None;
     Glop glop;
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshIndexedQuads(&mesh[0], count / 4)
             .setFillPaint(*paint, currentSnapshot()->alpha)
             .setTransform(*currentSnapshot(), transformFlags)
             .setModelViewOffsetRect(0, 0, Rect(left, top, right, bottom))
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     renderGlop(glop);
 }
@@ -2575,11 +2575,11 @@
             ? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None;
     Glop glop;
     GlopBuilder(mRenderState, mCaches, &glop)
+            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .setMeshUnitQuad()
             .setFillPaint(*paint, currentSnapshot()->alpha)
             .setTransform(*currentSnapshot(), transformFlags)
             .setModelViewMapUnitToRect(Rect(left, top, right, bottom))
-            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
             .build();
     renderGlop(glop);
 }
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 75e700a..b4cbc36 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -120,7 +120,10 @@
     ATRACE_CALL();
     LOG_ALWAYS_FATAL_IF(!info.damageAccumulator, "DamageAccumulator missing");
 
-    prepareTreeImpl(info);
+    // Functors don't correctly handle stencil usage of overdraw debugging - shove 'em in a layer.
+    bool functorsNeedLayer = Properties::debugOverdraw;
+
+    prepareTreeImpl(info, functorsNeedLayer);
 }
 
 void RenderNode::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
@@ -219,7 +222,15 @@
     }
 }
 
-void RenderNode::prepareTreeImpl(TreeInfo& info) {
+/**
+ * Traverse down the the draw tree to prepare for a frame.
+ *
+ * MODE_FULL = UI Thread-driven (thus properties must be synced), otherwise RT driven
+ *
+ * While traversing down the tree, functorsNeedLayer flag is set to true if anything that uses the
+ * stencil buffer may be needed. Views that use a functor to draw will be forced onto a layer.
+ */
+void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) {
     info.damageAccumulator->pushTransform(this);
 
     if (info.mode == TreeInfo::MODE_FULL) {
@@ -229,11 +240,17 @@
     if (CC_LIKELY(info.runAnimations)) {
         animatorDirtyMask = mAnimatorManager.animate(info);
     }
+
+    bool willHaveFunctor = info.mode == TreeInfo::MODE_FULL && mStagingDisplayListData
+            ? !mStagingDisplayListData->functors.isEmpty() : !mDisplayListData->functors.isEmpty();
+    bool childFunctorsNeedLayer = mProperties.prepareForFunctorPresence(
+            willHaveFunctor, functorsNeedLayer);
+
     prepareLayer(info, animatorDirtyMask);
     if (info.mode == TreeInfo::MODE_FULL) {
         pushStagingDisplayListChanges(info);
     }
-    prepareSubTree(info, mDisplayListData);
+    prepareSubTree(info, childFunctorsNeedLayer, mDisplayListData);
     pushLayerUpdate(info);
 
     info.damageAccumulator->popTransform();
@@ -313,7 +330,7 @@
     mDisplayListData = nullptr;
 }
 
-void RenderNode::prepareSubTree(TreeInfo& info, DisplayListData* subtree) {
+void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayListData* subtree) {
     if (subtree) {
         TextureCache& cache = Caches::getInstance().textureCache;
         info.out.hasFunctors |= subtree->functors.size();
@@ -324,7 +341,10 @@
             DrawRenderNodeOp* op = subtree->children()[i];
             RenderNode* childNode = op->mRenderNode;
             info.damageAccumulator->pushTransform(&op->mTransformFromParent);
-            childNode->prepareTreeImpl(info);
+            bool childFunctorsNeedLayer = functorsNeedLayer
+                    // Recorded with non-rect clip, or canvas-rotated by parent
+                    || op->mRecordedWithPotentialStencilClip;
+            childNode->prepareTreeImpl(info, childFunctorsNeedLayer);
             info.damageAccumulator->popTransform();
         }
     }
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index d0d81d9..025a4a4 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -235,10 +235,10 @@
         const char* mText;
     };
 
-    void prepareTreeImpl(TreeInfo& info);
+    void prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer);
     void pushStagingPropertiesChanges(TreeInfo& info);
     void pushStagingDisplayListChanges(TreeInfo& info);
-    void prepareSubTree(TreeInfo& info, DisplayListData* subtree);
+    void prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayListData* subtree);
     void applyLayerPropertiesToLayer(TreeInfo& info);
     void prepareLayer(TreeInfo& info, uint32_t dirtyMask);
     void pushLayerUpdate(TreeInfo& info);
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index 65c1c4a..81cf2df 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -158,6 +158,32 @@
         }
     }
 
+    /**
+     * Set internal layer state based on whether this layer
+     *
+     * Additionally, returns true if child RenderNodes with functors will need to use a layer
+     * to support clipping.
+     */
+    bool prepareForFunctorPresence(bool willHaveFunctor, bool ancestorDictatesFunctorsNeedLayer) {
+        // parent may have already dictated that a descendant layer is needed
+        bool functorsNeedLayer = ancestorDictatesFunctorsNeedLayer
+
+                // Round rect clipping forces layer for functors
+                || CC_UNLIKELY(getOutline().willClip())
+                || CC_UNLIKELY(getRevealClip().willClip())
+
+                // Complex matrices forces layer, due to stencil clipping
+                || CC_UNLIKELY(getTransformMatrix() && !getTransformMatrix()->isScaleTranslate())
+                || CC_UNLIKELY(getAnimationMatrix() && !getAnimationMatrix()->isScaleTranslate())
+                || CC_UNLIKELY(getStaticMatrix() && !getStaticMatrix()->isScaleTranslate());
+
+        mComputedFields.mNeedLayerForFunctors = (willHaveFunctor && functorsNeedLayer);
+
+        // If on a layer, will have consumed the need for isolating functors from stencil.
+        // Thus, it's safe to reset the flag until some descendent sets it.
+        return CC_LIKELY(effectiveLayerType() == LayerType::None) && functorsNeedLayer;
+    }
+
     RenderProperties& operator=(const RenderProperties& other);
 
     bool setClipToBounds(bool clipToBounds) {
@@ -580,15 +606,16 @@
     bool promotedToLayer() const {
         const int maxTextureSize = Caches::getInstance().maxTextureSize;
         return mLayerProperties.mType == LayerType::None
-                && !MathUtils::isZero(mPrimitiveFields.mAlpha)
-                && mPrimitiveFields.mAlpha < 1
-                && mPrimitiveFields.mHasOverlappingRendering
                 && mPrimitiveFields.mWidth <= maxTextureSize
-                && mPrimitiveFields.mHeight <= maxTextureSize;
+                && mPrimitiveFields.mHeight <= maxTextureSize
+                && (mComputedFields.mNeedLayerForFunctors
+                        || (!MathUtils::isZero(mPrimitiveFields.mAlpha)
+                                && mPrimitiveFields.mAlpha < 1
+                                && mPrimitiveFields.mHasOverlappingRendering));
     }
 
     LayerType effectiveLayerType() const {
-        return promotedToLayer() ? LayerType::RenderLayer : mLayerProperties.mType;
+        return CC_UNLIKELY(promotedToLayer()) ? LayerType::RenderLayer : mLayerProperties.mType;
     }
 
 private:
@@ -636,6 +663,9 @@
         SkMatrix* mTransformMatrix;
 
         Sk3DView mTransformCamera;
+
+        // Force layer on for functors to enable render features they don't yet support (clipping)
+        bool mNeedLayerForFunctors = false;
     } mComputedFields;
 };
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 33db9cf..a806440 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3701,8 +3701,9 @@
      * The message sent to apps when the contents of the device list changes if they provide
      * a {#link Handler} object to addOnAudioDeviceConnectionListener().
      */
-    private final static int MSG_DEVICES_DEVICES_ADDED = 0;
-    private final static int MSG_DEVICES_DEVICES_REMOVED = 1;
+    private final static int MSG_DEVICES_CALLBACK_REGISTERED = 0;
+    private final static int MSG_DEVICES_DEVICES_ADDED = 1;
+    private final static int MSG_DEVICES_DEVICES_REMOVED = 2;
 
     /**
      * The list of {@link AudioDeviceCallback} objects to receive add/remove notifications.
@@ -3848,8 +3849,10 @@
             android.os.Handler handler) {
         if (callback != null && !mDeviceCallbacks.containsKey(callback)) {
             synchronized (mDeviceCallbacks) {
-                mDeviceCallbacks.put(
-                        callback, new NativeEventHandlerDelegate(callback, handler));
+                NativeEventHandlerDelegate delegate =
+                        new NativeEventHandlerDelegate(callback, handler);
+                mDeviceCallbacks.put(callback, delegate);
+                broadcastDeviceListChange(delegate.getHandler());
             }
         }
     }
@@ -3868,49 +3871,60 @@
         }
     }
 
+    // Since we need to calculate the changes since THE LAST NOTIFICATION, and not since the
+    // (unpredictable) last time updateAudioPortCache() was called by someone, keep a list
+    // of the ports that exist at the time of the last notification.
+    private ArrayList<AudioDevicePort> mPreviousPorts = new ArrayList<AudioDevicePort>();
+
     /**
      * Internal method to compute and generate add/remove messages and then send to any
      * registered callbacks.
      */
-    private void broadcastDeviceListChange() {
+    private void broadcastDeviceListChange(Handler handler) {
         int status;
 
-        ArrayList<AudioDevicePort> previous_ports = new ArrayList<AudioDevicePort>();
-        status = AudioManager.listPreviousAudioDevicePorts(previous_ports);
-        if (status != AudioManager.SUCCESS) {
-            return;
-        }
-
+        // Get the new current set of ports
         ArrayList<AudioDevicePort> current_ports = new ArrayList<AudioDevicePort>();
         status = AudioManager.listAudioDevicePorts(current_ports);
         if (status != AudioManager.SUCCESS) {
             return;
         }
 
-        AudioDeviceInfo[] added_devices =
-                calcListDeltas(previous_ports, current_ports, GET_DEVICES_ALL);
-        AudioDeviceInfo[] removed_devices =
-                calcListDeltas(current_ports, previous_ports, GET_DEVICES_ALL);
+        if (handler != null) {
+            // This is the callback for the registration, so send the current list
+            AudioDeviceInfo[] deviceList =
+                    infoListFromPortList(current_ports, GET_DEVICES_ALL);
+            handler.sendMessage(
+                    Message.obtain(handler, MSG_DEVICES_CALLBACK_REGISTERED, deviceList));
+        } else {
+            AudioDeviceInfo[] added_devices =
+                    calcListDeltas(mPreviousPorts, current_ports, GET_DEVICES_ALL);
+            AudioDeviceInfo[] removed_devices =
+                    calcListDeltas(current_ports, mPreviousPorts, GET_DEVICES_ALL);
 
-        if (added_devices.length != 0 || removed_devices.length != 0) {
-            Collection<NativeEventHandlerDelegate> values;
-            synchronized (mDeviceCallbacks) {
-                values = mDeviceCallbacks.values();
-            }
-            for (NativeEventHandlerDelegate delegate : values) {
-                Handler handler = delegate.getHandler();
-                if (handler != null) {
-                    if (added_devices.length != 0) {
-                        handler.sendMessage(
-                            Message.obtain(handler,MSG_DEVICES_DEVICES_ADDED, added_devices));
-                    }
-                    if (removed_devices.length != 0) {
-                        handler.sendMessage(
-                            Message.obtain(handler,MSG_DEVICES_DEVICES_REMOVED, removed_devices));
+            if (added_devices.length != 0 || removed_devices.length != 0) {
+                Collection<NativeEventHandlerDelegate> values;
+                synchronized (mDeviceCallbacks) {
+                    values = mDeviceCallbacks.values();
+                }
+                for (NativeEventHandlerDelegate delegate : values) {
+                    handler = delegate.getHandler();
+                    if (handler != null) {
+                        if (added_devices.length != 0) {
+                            handler.sendMessage(
+                                Message.obtain(handler,MSG_DEVICES_DEVICES_ADDED, added_devices));
+                        }
+                        if (removed_devices.length != 0) {
+                            handler.sendMessage(
+                                Message.obtain(handler,MSG_DEVICES_DEVICES_REMOVED,
+                                               removed_devices));
+                        }
                     }
                 }
             }
         }
+
+        mPreviousPorts = current_ports;
     }
 
     /**
@@ -3919,7 +3933,7 @@
     private class OnAmPortUpdateListener implements AudioManager.OnAudioPortUpdateListener {
         static final String TAG = "OnAmPortUpdateListener";
         public void onAudioPortListUpdate(AudioPort[] portList) {
-            broadcastDeviceListChange();
+            broadcastDeviceListChange(null);
         }
 
         /**
@@ -3933,7 +3947,7 @@
          * Callback method called when the mediaserver dies
          */
         public void onServiceDied() {
-            broadcastDeviceListChange();
+            broadcastDeviceListChange(null);
         }
     }
 
@@ -3965,8 +3979,8 @@
                     @Override
                     public void handleMessage(Message msg) {
                         switch(msg.what) {
+                        case MSG_DEVICES_CALLBACK_REGISTERED:
                         case MSG_DEVICES_DEVICES_ADDED:
-                            // call the OnAudioDeviceConnectionListener
                             if (callback != null) {
                                 callback.onAudioDevicesAdded((AudioDeviceInfo[])msg.obj);
                             }
diff --git a/media/java/android/media/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl
index e30796d..c2cc2b9 100644
--- a/media/java/android/media/midi/IMidiDeviceServer.aidl
+++ b/media/java/android/media/midi/IMidiDeviceServer.aidl
@@ -31,4 +31,5 @@
     void connectPorts(IBinder token, in ParcelFileDescriptor pfd, int outputPortNumber);
 
     MidiDeviceInfo getDeviceInfo();
+    void setDeviceInfo(in MidiDeviceInfo deviceInfo);
 }
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index 1b56e1c..1212b64 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -269,8 +269,20 @@
         public MidiDeviceInfo getDeviceInfo() {
             return mDeviceInfo;
         }
+
+        @Override
+        public void setDeviceInfo(MidiDeviceInfo deviceInfo) {
+            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+                throw new SecurityException("setDeviceInfo should only be called by MidiService");
+            }
+            if (mDeviceInfo != null) {
+                throw new IllegalStateException("setDeviceInfo should only be called once");
+            }
+            mDeviceInfo = deviceInfo;
+        }
     };
 
+    // Constructor for MidiManager.createDeviceServer()
     /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
             int numOutputPorts, Callback callback) {
         mMidiManager = midiManager;
@@ -292,6 +304,13 @@
         mGuard.open("close");
     }
 
+    // Constructor for MidiDeviceService.onCreate()
+    /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
+           MidiDeviceInfo deviceInfo, Callback callback) {
+        this(midiManager, inputPortReceivers, deviceInfo.getOutputPortCount(), callback);
+        mDeviceInfo = deviceInfo;
+    }
+
     /* package */ IMidiDeviceServer getBinderInterface() {
         return mServer;
     }
@@ -300,13 +319,6 @@
         return mServer.asBinder();
     }
 
-    /* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) {
-        if (mDeviceInfo != null) {
-            throw new IllegalStateException("setDeviceInfo should only be called once");
-        }
-        mDeviceInfo = deviceInfo;
-    }
-
     private void updateDeviceStatus() {
         // clear calling identity, since we may be in a Binder call from one of our clients
         long identityToken = Binder.clearCallingIdentity();
diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java
index d897ad2..388d95b 100644
--- a/media/java/android/media/midi/MidiDeviceService.java
+++ b/media/java/android/media/midi/MidiDeviceService.java
@@ -83,9 +83,7 @@
             if (inputPortReceivers == null) {
                 inputPortReceivers = new MidiReceiver[0];
             }
-            server = new MidiDeviceServer(mMidiManager, inputPortReceivers,
-                    deviceInfo.getOutputPortCount(), mCallback);
-            server.setDeviceInfo(deviceInfo);
+            server = new MidiDeviceServer(mMidiManager, inputPortReceivers, deviceInfo, mCallback);
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in IMidiManager.getServiceDeviceInfo");
             server = null;
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index 0beb9a4..89230fe 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -318,7 +318,6 @@
                 Log.e(TAG, "registerVirtualDevice failed");
                 return null;
             }
-            server.setDeviceInfo(deviceInfo);
             return server;
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in createVirtualDevice");
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 50a215c..f52ccc9 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -434,9 +434,12 @@
         }
 
         /**
-         * Informs the application that the video is now available for watching. This is primarily
-         * used to signal the application to unblock the screen. The TV input service must call this
-         * method as soon as the content rendered onto its surface gets ready for viewing.
+         * Informs the application that the video is now available for watching. Video is blocked
+         * until this method is called.
+         *
+         * <p>The TV input service must call this method as soon as the content rendered onto its
+         * surface is ready for viewing. This method must be called each time {@link #onTune(Uri)}
+         * is called.
          *
          * @see #notifyVideoUnavailable
          */
@@ -761,9 +764,11 @@
         public abstract void onSetStreamVolume(float volume);
 
         /**
-         * Tunes to a given channel. When the video is available, {@link #notifyVideoAvailable()}
-         * should be called. Also, {@link #notifyVideoUnavailable(int)} should be called when the TV
-         * input cannot continue playing the given channel.
+         * Tunes to a given channel.
+         *
+         * <p>No video will be displayed until {@link #notifyVideoAvailable()} is called.
+         * Also, {@link #notifyVideoUnavailable(int)} should be called when the TV input cannot
+         * continue playing the given channel.
          *
          * @param channelUri The URI of the channel.
          * @return {@code true} if the tuning was successful, {@code false} otherwise.
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index aa7d1f8..2427363 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -991,16 +991,6 @@
     }
 
     private void onDiskScannedLocked(DiskInfo disk) {
-        final Intent intent = new Intent(DiskInfo.ACTION_DISK_SCANNED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
-                android.Manifest.permission.WRITE_MEDIA_STORAGE);
-
-        final CountDownLatch latch = mDiskScanLatches.remove(disk.id);
-        if (latch != null) {
-            latch.countDown();
-        }
-
         int volumeCount = 0;
         for (int i = 0; i < mVolumes.size(); i++) {
             final VolumeInfo vol = mVolumes.valueAt(i);
@@ -1009,6 +999,18 @@
             }
         }
 
+        final Intent intent = new Intent(DiskInfo.ACTION_DISK_SCANNED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(DiskInfo.EXTRA_DISK_ID, disk.id);
+        intent.putExtra(DiskInfo.EXTRA_VOLUME_COUNT, volumeCount);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                android.Manifest.permission.WRITE_MEDIA_STORAGE);
+
+        final CountDownLatch latch = mDiskScanLatches.remove(disk.id);
+        if (latch != null) {
+            latch.countDown();
+        }
+
         disk.volumeCount = volumeCount;
         mCallbacks.notifyDiskScanned(disk, volumeCount);
     }
@@ -1060,6 +1062,7 @@
 
     private boolean isBroadcastWorthy(VolumeInfo vol) {
         switch (vol.getType()) {
+            case VolumeInfo.TYPE_PRIVATE:
             case VolumeInfo.TYPE_PUBLIC:
             case VolumeInfo.TYPE_EMULATED:
                 break;
@@ -1072,6 +1075,7 @@
             case VolumeInfo.STATE_MOUNTED_READ_ONLY:
             case VolumeInfo.STATE_EJECTING:
             case VolumeInfo.STATE_UNMOUNTED:
+            case VolumeInfo.STATE_UNMOUNTABLE:
                 break;
             default:
                 return false;
@@ -1098,6 +1102,8 @@
 
         if (isBroadcastWorthy(vol)) {
             final Intent intent = new Intent(VolumeInfo.ACTION_VOLUME_STATE_CHANGED);
+            intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id);
+            intent.putExtra(VolumeInfo.EXTRA_VOLUME_STATE, newState);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
                     android.Manifest.permission.WRITE_MEDIA_STORAGE);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index b97fa69..b56e326 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1113,23 +1113,32 @@
             // correct delta from when we should start reading (aka when we are on battery).
             WifiActivityEnergyInfo info = mWifiManager.reportActivityInfo();
             if (info != null && info.isValid()) {
+                if (info.mControllerEnergyUsed < 0 || info.mControllerIdleTimeMs < 0 ||
+                        info.mControllerRxTimeMs < 0 || info.mControllerTxTimeMs < 0) {
+                    Slog.wtf(TAG, "Reported WiFi energy data is invalid: " + info);
+                    return null;
+                }
+
                 // We will modify the last info object to be the delta, and store the new
                 // WifiActivityEnergyInfo object as our last one.
                 final WifiActivityEnergyInfo result = mLastInfo;
                 result.mTimestamp = info.getTimeStamp();
                 result.mStackState = info.getStackState();
+
+                // These times seem to be the most reliable.
                 result.mControllerTxTimeMs =
                         info.mControllerTxTimeMs - mLastInfo.mControllerTxTimeMs;
                 result.mControllerRxTimeMs =
                         info.mControllerRxTimeMs - mLastInfo.mControllerRxTimeMs;
-                result.mControllerEnergyUsed =
-                        info.mControllerEnergyUsed - mLastInfo.mControllerEnergyUsed;
 
                 // WiFi calculates the idle time as a difference from the on time and the various
                 // Rx + Tx times. There seems to be some missing time there because this sometimes
                 // becomes negative. Just cap it at 0 and move on.
+                // b/21613534
                 result.mControllerIdleTimeMs =
                         Math.max(0, info.mControllerIdleTimeMs - mLastInfo.mControllerIdleTimeMs);
+                result.mControllerEnergyUsed =
+                        Math.max(0, info.mControllerEnergyUsed - mLastInfo.mControllerEnergyUsed);
 
                 if (result.mControllerTxTimeMs < 0 ||
                         result.mControllerRxTimeMs < 0) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index cd467bd..d39b25f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2852,6 +2852,49 @@
         }
     }
 
+    void setBtScoDeviceConnectionState(BluetoothDevice btDevice, int state) {
+        if (btDevice == null) {
+            return;
+        }
+
+        String address = btDevice.getAddress();
+        BluetoothClass btClass = btDevice.getBluetoothClass();
+        int outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
+        int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
+        if (btClass != null) {
+            switch (btClass.getDeviceClass()) {
+            case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+            case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
+                outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
+                break;
+            case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+                outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
+                break;
+            }
+        }
+
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+
+        boolean connected = (state == BluetoothProfile.STATE_CONNECTED);
+
+        String btDeviceName =  btDevice.getName();
+        boolean success =
+            handleDeviceConnection(connected, outDevice, address, btDeviceName) &&
+            handleDeviceConnection(connected, inDevice, address, btDeviceName);
+        if (success) {
+            synchronized (mScoClients) {
+                if (connected) {
+                    mBluetoothHeadsetDevice = btDevice;
+                } else {
+                    mBluetoothHeadsetDevice = null;
+                    resetBluetoothSco();
+                }
+            }
+        }
+    }
+
     private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
         new BluetoothProfile.ServiceListener() {
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
@@ -3002,6 +3045,10 @@
 
             case BluetoothProfile.HEADSET:
                 synchronized (mScoClients) {
+                    if (mBluetoothHeadsetDevice != null) {
+                        setBtScoDeviceConnectionState(mBluetoothHeadsetDevice,
+                                BluetoothProfile.STATE_DISCONNECTED);
+                    }
                     mBluetoothHeadset = null;
                 }
                 break;
@@ -4894,49 +4941,9 @@
             } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
                 state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
                                                BluetoothProfile.STATE_DISCONNECTED);
-                outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
-                inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
-                String address = null;
-
                 BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                if (btDevice == null) {
-                    return;
-                }
 
-                address = btDevice.getAddress();
-                BluetoothClass btClass = btDevice.getBluetoothClass();
-                if (btClass != null) {
-                    switch (btClass.getDeviceClass()) {
-                    case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
-                    case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
-                        outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
-                        break;
-                    case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
-                        outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
-                        break;
-                    }
-                }
-
-                if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-                    address = "";
-                }
-
-                boolean connected = (state == BluetoothProfile.STATE_CONNECTED);
-
-                String btDeviceName =  btDevice.getName();
-                boolean success =
-                    handleDeviceConnection(connected, outDevice, address, btDeviceName) &&
-                    handleDeviceConnection(connected, inDevice, address, btDeviceName);
-                if (success) {
-                    synchronized (mScoClients) {
-                        if (connected) {
-                            mBluetoothHeadsetDevice = btDevice;
-                        } else {
-                            mBluetoothHeadsetDevice = null;
-                            resetBluetoothSco();
-                        }
-                    }
-                }
+                setBtScoDeviceConnectionState(btDevice, state);
             } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
                 boolean broadcast = false;
                 int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index 7531403..1ee07a5 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -277,6 +277,11 @@
         return mKeySets.get(keySetId);
     }
 
+    /* Checks if an identifier refers to a known keyset */
+    public boolean isIdValidKeySetId(long id) {
+        return mKeySets.get(id) != null;
+    }
+
     /**
      * Fetches the {@link PublicKey public keys} which belong to the specified
      * KeySet id.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6c9fd3f..99b24ed 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6157,7 +6157,24 @@
 
             pkg.applicationInfo.uid = pkgSetting.appId;
             pkg.mExtras = pkgSetting;
-            if (!pkgSetting.keySetData.isUsingUpgradeKeySets() || pkgSetting.sharedUser != null) {
+            if (shouldCheckUpgradeKeySetLP(pkgSetting, scanFlags)) {
+                if (checkUpgradeKeySetLP(pkgSetting, pkg)) {
+                    // We just determined the app is signed correctly, so bring
+                    // over the latest parsed certs.
+                    pkgSetting.signatures.mSignatures = pkg.mSignatures;
+                } else {
+                    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                        throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                                "Package " + pkg.packageName + " upgrade keys do not match the "
+                                + "previously installed version");
+                    } else {
+                        pkgSetting.signatures.mSignatures = pkg.mSignatures;
+                        String msg = "System package " + pkg.packageName
+                            + " signature changed; retaining data.";
+                        reportSettingsProblem(Log.WARN, msg);
+                    }
+                }
+            } else {
                 try {
                     verifySignaturesLP(pkgSetting, pkg);
                     // We just determined the app is signed correctly, so bring
@@ -6189,23 +6206,6 @@
                         + " signature changed; retaining data.";
                     reportSettingsProblem(Log.WARN, msg);
                 }
-            } else {
-                if (!checkUpgradeKeySetLP(pkgSetting, pkg)) {
-                    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
-                        throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
-                                "Package " + pkg.packageName + " upgrade keys do not match the "
-                                + "previously installed version");
-                    } else {
-                        pkgSetting.signatures.mSignatures = pkg.mSignatures;
-                        String msg = "System package " + pkg.packageName
-                            + " signature changed; retaining data.";
-                        reportSettingsProblem(Log.WARN, msg);
-                    }
-                } else {
-                    // We just determined the app is signed correctly, so bring
-                    // over the latest parsed certs.
-                    pkgSetting.signatures.mSignatures = pkg.mSignatures;
-                }
             }
             // Verify that this new package doesn't have any content providers
             // that conflict with existing packages.  Only do this if the
@@ -11161,6 +11161,28 @@
         }
     }
 
+    private boolean shouldCheckUpgradeKeySetLP(PackageSetting oldPs, int scanFlags) {
+        // Can't rotate keys during boot or if sharedUser.
+        if (oldPs == null || (scanFlags&SCAN_BOOTING) != 0 || oldPs.sharedUser != null
+                || !oldPs.keySetData.isUsingUpgradeKeySets()) {
+            return false;
+        }
+        // app is using upgradeKeySets; make sure all are valid
+        KeySetManagerService ksms = mSettings.mKeySetManagerService;
+        long[] upgradeKeySets = oldPs.keySetData.getUpgradeKeySets();
+        for (int i = 0; i < upgradeKeySets.length; i++) {
+            if (!ksms.isIdValidKeySetId(upgradeKeySets[i])) {
+                Slog.wtf(TAG, "Package "
+                         + (oldPs.name != null ? oldPs.name : "<null>")
+                         + " contains upgrade-key-set reference to unknown key-set: "
+                         + upgradeKeySets[i]
+                         + " reverting to signatures check.");
+                return false;
+            }
+        }
+        return true;
+    }
+
     private boolean checkUpgradeKeySetLP(PackageSetting oldPS, PackageParser.Package newPkg) {
         // Upgrade keysets are being used.  Determine if new package has a superset of the
         // required keys.
@@ -11189,7 +11211,14 @@
             oldPackage = mPackages.get(pkgName);
             if (DEBUG_INSTALL) Slog.d(TAG, "replacePackageLI: new=" + pkg + ", old=" + oldPackage);
             final PackageSetting ps = mSettings.mPackages.get(pkgName);
-            if (ps == null || !ps.keySetData.isUsingUpgradeKeySets() || ps.sharedUser != null) {
+            if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) {
+                if(!checkUpgradeKeySetLP(ps, pkg)) {
+                    res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                            "New package not signed by keys specified by upgrade-keysets: "
+                            + pkgName);
+                    return;
+                }
+            } else {
                 // default to original signature matching
                 if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures)
                     != PackageManager.SIGNATURE_MATCH) {
@@ -11197,13 +11226,6 @@
                             "New package has a different signature: " + pkgName);
                     return;
                 }
-            } else {
-                if(!checkUpgradeKeySetLP(ps, pkg)) {
-                    res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
-                            "New package not signed by keys specified by upgrade-keysets: "
-                            + pkgName);
-                    return;
-                }
             }
 
             // In case of rollback, remember per-user/profile install state
@@ -11633,20 +11655,20 @@
                 // Quick sanity check that we're signed correctly if updating;
                 // we'll check this again later when scanning, but we want to
                 // bail early here before tripping over redefined permissions.
-                if (!ps.keySetData.isUsingUpgradeKeySets() || ps.sharedUser != null) {
-                    try {
-                        verifySignaturesLP(ps, pkg);
-                    } catch (PackageManagerException e) {
-                        res.setError(e.error, e.getMessage());
-                        return;
-                    }
-                } else {
+                if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) {
                     if (!checkUpgradeKeySetLP(ps, pkg)) {
                         res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
                                 + pkg.packageName + " upgrade keys do not match the "
                                 + "previously installed version");
                         return;
                     }
+                } else {
+                    try {
+                        verifySignaturesLP(ps, pkg);
+                    } catch (PackageManagerException e) {
+                        res.setError(e.error, e.getMessage());
+                        return;
+                    }
                 }
 
                 oldCodePath = mSettings.mPackages.get(pkgName).codePathString;
@@ -11667,14 +11689,14 @@
                     // also includes the "updating the same package" case, of course.
                     // "updating same package" could also involve key-rotation.
                     final boolean sigsOk;
-                    if (!bp.sourcePackage.equals(pkg.packageName)
-                            || !(bp.packageSetting instanceof PackageSetting)
-                            || !bp.packageSetting.keySetData.isUsingUpgradeKeySets()
-                            || ((PackageSetting) bp.packageSetting).sharedUser != null) {
+                    if (bp.sourcePackage.equals(pkg.packageName)
+                            && (bp.packageSetting instanceof PackageSetting)
+                            && (shouldCheckUpgradeKeySetLP((PackageSetting) bp.packageSetting,
+                                    scanFlags))) {
+                        sigsOk = checkUpgradeKeySetLP((PackageSetting) bp.packageSetting, pkg);
+                    } else {
                         sigsOk = compareSignatures(bp.packageSetting.signatures.mSignatures,
                                 pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
-                    } else {
-                        sigsOk = checkUpgradeKeySetLP((PackageSetting) bp.packageSetting, pkg);
                     }
                     if (!sigsOk) {
                         // If the owning package is the system itself, we log but allow
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 25ca167..dbcfa19 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -243,7 +243,7 @@
     }
 
     /** Amount of time (in milliseconds) to wait for windows drawn before powering on. */
-    static final int WAITING_FOR_DRAWN_TIMEOUT = 1000;
+    static final int WAITING_FOR_DRAWN_TIMEOUT = 500;
 
     /**
      * Lock protecting internal state.  Must not call out into window
@@ -836,13 +836,18 @@
             // If sensor is turned off or nonexistent for some reason
             return;
         }
-        //Could have been invoked due to screen turning on or off or
-        //change of the currently visible window's orientation
+        // Could have been invoked due to screen turning on or off or
+        // change of the currently visible window's orientation.
         if (localLOGV) Slog.v(TAG, "mScreenOnEarly=" + mScreenOnEarly
                 + ", mAwake=" + mAwake + ", mCurrentAppOrientation=" + mCurrentAppOrientation
-                + ", mOrientationSensorEnabled=" + mOrientationSensorEnabled);
+                + ", mOrientationSensorEnabled=" + mOrientationSensorEnabled
+                + ", mKeyguardDrawComplete=" + mKeyguardDrawComplete
+                + ", mWindowManagerDrawComplete=" + mWindowManagerDrawComplete);
         boolean disable = true;
-        if (mScreenOnEarly && mAwake) {
+        // Note: We postpone the rotating of the screen until the keyguard as well as the
+        // window manager have reported a draw complete.
+        if (mScreenOnEarly && mAwake &&
+                mKeyguardDrawComplete && mWindowManagerDrawComplete) {
             if (needSensorRunningLp()) {
                 disable = false;
                 //enable listener if not already enabled
@@ -5369,7 +5374,7 @@
     private void finishKeyguardDrawn() {
         synchronized (mLock) {
             if (!mAwake || mKeyguardDrawComplete) {
-                return; // spurious
+                return; // We are not awake yet or we have already informed of this event.
             }
 
             mKeyguardDrawComplete = true;
@@ -5407,18 +5412,18 @@
             mScreenOnFully = false;
             mWindowManagerDrawComplete = false;
             mScreenOnListener = screenOnListener;
-            updateOrientationListenerLp();
         }
 
         mWindowManagerInternal.waitForAllWindowsDrawn(mWindowManagerDrawCallback,
                 WAITING_FOR_DRAWN_TIMEOUT);
-        // ... eventually calls finishWindowsDrawn
+        // ... eventually calls finishWindowsDrawn which will finalize our screen turn on
+        // as well as enabling the orientation change logic/sensor.
     }
 
     private void finishWindowsDrawn() {
         synchronized (mLock) {
             if (!mScreenOnEarly || mWindowManagerDrawComplete) {
-                return; // spurious
+                return; // Screen is not turned on or we did already handle this case earlier.
             }
 
             mWindowManagerDrawComplete = true;
@@ -5428,6 +5433,11 @@
     }
 
     private void finishScreenTurningOn() {
+        synchronized (mLock) {
+            // We have just finished drawing screen content. Since the orientation listener
+            // gets only installed when all windows are drawn, we try to install it again.
+            updateOrientationListenerLp();
+        }
         final ScreenOnListener listener;
         final boolean enableScreen;
         synchronized (mLock) {
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index d1bbbfc..3ecfd55 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -741,6 +741,15 @@
         MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts,
                 inputPortNames, outputPortNames, properties, isPrivate);
 
+        if (server != null) {
+            try {
+                server.setDeviceInfo(deviceInfo);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException in setDeviceInfo()");
+                return null;
+            }
+        }
+
         Device device = null;
         BluetoothDevice bluetoothDevice = null;
         if (type == MidiDeviceInfo.TYPE_BLUETOOTH) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 8e1895e..837570b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -41,7 +41,6 @@
 import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.hardware.display.DisplayManager;
-import android.net.Uri;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Binder;
@@ -61,8 +60,10 @@
 import android.telephony.TelephonyManager;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.KeyValueListParser;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.TimeUtils;
 import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
@@ -101,16 +102,11 @@
     private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
     private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
 
-    static final long DEFAULT_APP_IDLE_THRESHOLD_MILLIS = COMPRESS_TIME ? ONE_MINUTE * 4
-            : 12 * 60 * ONE_MINUTE; // 12 hours of screen-on time sans dream-time
-    static final long DEFAULT_WALLCLOCK_APP_IDLE_THRESHOLD_MILLIS = COMPRESS_TIME ? ONE_MINUTE * 8
-            : 2L * 24 * 60 * ONE_MINUTE; // 2 days
-    static final long DEFAULT_CHECK_IDLE_INTERVAL = COMPRESS_TIME ? ONE_MINUTE
-            : 8 * 60 * ONE_MINUTE; // 8 hours
-    static final long DEFAULT_PAROLE_INTERVAL = COMPRESS_TIME ? ONE_MINUTE * 10
-            : 24 * 60 * ONE_MINUTE; // 24 hours between paroles
-    static final long DEFAULT_PAROLE_DURATION = COMPRESS_TIME ? ONE_MINUTE
-            : 10 * ONE_MINUTE; // 10 minutes
+    long mAppIdleDurationMillis;
+    long mCheckIdleIntervalMillis;
+    long mAppIdleWallclockThresholdMillis;
+    long mAppIdleParoleIntervalMillis;
+    long mAppIdleParoleDurationMillis;
 
     // Handler message types.
     static final int MSG_REPORT_EVENT = 0;
@@ -140,8 +136,7 @@
     boolean mAppIdleParoled;
     private boolean mScreenOn;
     private long mLastAppIdleParoledTime;
-    long mAppIdleDurationMillis;
-    long mCheckIdleIntervalMillis = DEFAULT_CHECK_IDLE_INTERVAL;
+
     long mScreenOnTime;
     long mScreenOnSystemTimeSnapshot;
 
@@ -185,11 +180,7 @@
 
         mRealTimeSnapshot = SystemClock.elapsedRealtime();
         mSystemTimeSnapshot = System.currentTimeMillis();
-        // Look at primary user's secure setting for this. TODO: Maybe apply different
-        // thresholds for different users.
-        mAppIdleDurationMillis = Settings.Secure.getLongForUser(getContext().getContentResolver(),
-                Settings.Secure.APP_IDLE_DURATION, DEFAULT_APP_IDLE_THRESHOLD_MILLIS,
-                UserHandle.USER_OWNER);
+
 
         publishLocalService(UsageStatsManagerInternal.class, new LocalService());
         publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
@@ -199,7 +190,10 @@
     public void onBootPhase(int phase) {
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
             // Observe changes to the threshold
-            new SettingsObserver(mHandler).registerObserver();
+            SettingsObserver settingsObserver = new SettingsObserver(mHandler);
+            settingsObserver.registerObserver();
+            settingsObserver.updateSettings();
+
             mAppWidgetManager = getContext().getSystemService(AppWidgetManager.class);
             mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
                     ServiceManager.getService(DeviceIdleController.SERVICE_NAME));
@@ -323,7 +317,7 @@
         // Compute when the next parole needs to happen. We check more frequently than necessary
         // since the message handler delays are based on elapsedRealTime and not wallclock time.
         // The comparison is done in wallclock time.
-        long timeLeft = (mLastAppIdleParoledTime + DEFAULT_PAROLE_INTERVAL)
+        long timeLeft = (mLastAppIdleParoledTime + mAppIdleParoleIntervalMillis)
                 - checkAndGetTimeLocked();
         if (timeLeft < 0) {
             timeLeft = 0;
@@ -334,7 +328,7 @@
     private void postParoleEndTimeout() {
         if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_END_TIMEOUT");
         mHandler.removeMessages(MSG_PAROLE_END_TIMEOUT);
-        mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, DEFAULT_PAROLE_DURATION);
+        mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, mAppIdleParoleDurationMillis);
     }
 
     void postCheckIdleStates(int userId) {
@@ -386,7 +380,7 @@
         synchronized (mLock) {
             if (!mAppIdleParoled) {
                 final long timeSinceLastParole = checkAndGetTimeLocked() - mLastAppIdleParoledTime;
-                if (timeSinceLastParole > DEFAULT_PAROLE_INTERVAL) {
+                if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
                     if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
                     setAppIdleParoled(true);
                     // Make sure it ends at some point
@@ -475,7 +469,7 @@
         synchronized (mLock) {
             final long timeSinceLastParole = checkAndGetTimeLocked() - mLastAppIdleParoledTime;
             if (!deviceIdle
-                    && timeSinceLastParole >= DEFAULT_PAROLE_INTERVAL) {
+                    && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
                 if (DEBUG) Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
                 postNextParoleTimeout();
                 setAppIdleParoled(true);
@@ -608,7 +602,7 @@
                     lastUsedTime, screenOnTime, timeNow);
             service.setBeginIdleTime(packageName, deviceUsageTime);
             service.setSystemLastUsedTime(packageName,
-                    timeNow - (idle ? DEFAULT_WALLCLOCK_APP_IDLE_THRESHOLD_MILLIS : 0) - 5000);
+                    timeNow - (idle ? mAppIdleWallclockThresholdMillis : 0) - 5000);
             // Inform listeners if necessary
             if (previouslyIdle != idle) {
                 // Slog.d(TAG, "Informing listeners of out-of-idle " + event.mPackage);
@@ -711,7 +705,7 @@
     boolean hasPassedIdleTimeoutLocked(long beginIdleTime, long lastUsedTime,
             long screenOnTime, long currentTime) {
         return (beginIdleTime <= screenOnTime - mAppIdleDurationMillis)
-                && (lastUsedTime <= currentTime - DEFAULT_WALLCLOCK_APP_IDLE_THRESHOLD_MILLIS);
+                && (lastUsedTime <= currentTime - mAppIdleWallclockThresholdMillis);
     }
 
     void addListener(AppIdleStateChangeListener listener) {
@@ -854,7 +848,30 @@
                 }
                 idpw.decreaseIndent();
             }
-            pw.write("Screen On Timebase:" + mScreenOnTime + "\n");
+            pw.println("Screen On Timebase:" + mScreenOnTime);
+
+            pw.println();
+            pw.println("Settings:");
+
+            pw.print("  mAppIdleDurationMillis=");
+            TimeUtils.formatDuration(mAppIdleDurationMillis, pw);
+            pw.println();
+
+            pw.print("  mAppIdleWallclockThresholdMillis=");
+            TimeUtils.formatDuration(mAppIdleWallclockThresholdMillis, pw);
+            pw.println();
+
+            pw.print("  mCheckIdleIntervalMillis=");
+            TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw);
+            pw.println();
+
+            pw.print("  mAppIdleParoleIntervalMillis=");
+            TimeUtils.formatDuration(mAppIdleParoleIntervalMillis, pw);
+            pw.println();
+
+            pw.print("  mAppIdleParoleDurationMillis=");
+            TimeUtils.formatDuration(mAppIdleParoleDurationMillis, pw);
+            pw.println();
         }
     }
 
@@ -907,28 +924,61 @@
     }
 
     /**
-     * Observe settings changes for Settings.Secure.APP_IDLE_DURATION.
+     * Observe settings changes for {@link Settings.Global#APP_IDLE_CONSTANTS}.
      */
     private class SettingsObserver extends ContentObserver {
+        private static final String KEY_IDLE_DURATION = "idle_duration";
+        private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold";
+        private static final String KEY_PAROLE_INTERVAL = "parole_interval";
+        private static final String KEY_PAROLE_DURATION = "parole_duration";
+
+        private final KeyValueListParser mParser = new KeyValueListParser(',');
 
         SettingsObserver(Handler handler) {
             super(handler);
         }
 
         void registerObserver() {
-            getContext().getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
-                    Settings.Secure.APP_IDLE_DURATION), false, this, UserHandle.USER_OWNER);
+            getContext().getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.APP_IDLE_CONSTANTS), false, this);
         }
 
         @Override
-        public void onChange(boolean selfChange, Uri uri, int userId) {
-            mAppIdleDurationMillis = Settings.Secure.getLongForUser(getContext().getContentResolver(),
-                    Settings.Secure.APP_IDLE_DURATION, DEFAULT_APP_IDLE_THRESHOLD_MILLIS,
-                    UserHandle.USER_OWNER);
-            mCheckIdleIntervalMillis = Math.min(DEFAULT_CHECK_IDLE_INTERVAL,
-                    mAppIdleDurationMillis / 4);
+        public void onChange(boolean selfChange) {
+            updateSettings();
             postCheckIdleStates(UserHandle.USER_ALL);
         }
+
+        void updateSettings() {
+            synchronized (mLock) {
+                // Look at global settings for this.
+                // TODO: Maybe apply different thresholds for different users.
+                try {
+                    mParser.setString(Settings.Global.getString(getContext().getContentResolver(),
+                            Settings.Global.APP_IDLE_CONSTANTS));
+                } catch (IllegalArgumentException e) {
+                    Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
+                    // fallthrough, mParser is empty and all defaults will be returned.
+                }
+
+                // Default: 12 hours of screen-on time sans dream-time
+                mAppIdleDurationMillis = mParser.getLong(KEY_IDLE_DURATION,
+                       COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);
+
+                mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
+                        COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
+
+                mCheckIdleIntervalMillis = Math.min(mAppIdleDurationMillis / 4,
+                        COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours
+
+                // Default: 24 hours between paroles
+                mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
+                        COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
+
+                mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
+                        COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
+            }
+        }
     }
 
     private class BinderService extends IUsageStatsManager.Stub {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 9a63aa3..91566f8 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -448,8 +448,13 @@
 
         /**
          * Stores a list of the video callbacks, keyed by IBinder.
+         *
+         * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+         * load factor before resizing, 1 means we only expect a single thread to
+         * access the map so make only a single shard
          */
-        private HashMap<IBinder, IVideoCallback> mVideoCallbacks = new HashMap<>();
+        private ConcurrentHashMap<IBinder, IVideoCallback> mVideoCallbacks =
+                new ConcurrentHashMap<IBinder, IVideoCallback>(8, 0.9f, 1);
 
         /**
          * Default handler used to consolidate binder method calls onto a single thread.
@@ -470,12 +475,16 @@
                         IBinder binder = (IBinder) msg.obj;
                         IVideoCallback callback = IVideoCallback.Stub
                                 .asInterface((IBinder) msg.obj);
+                        if (callback == null) {
+                            Log.w(this, "addVideoProvider - skipped; callback is null.");
+                            break;
+                        }
+
                         if (mVideoCallbacks.containsKey(binder)) {
                             Log.i(this, "addVideoProvider - skipped; already present.");
                             break;
                         }
                         mVideoCallbacks.put(binder, callback);
-                        Log.i(this, "addVideoProvider  "+ mVideoCallbacks.size());
                         break;
                     }
                     case MSG_REMOVE_VIDEO_CALLBACK: {
@@ -594,7 +603,7 @@
 
         public VideoProvider() {
             mBinder = new VideoProvider.VideoProviderBinder();
-            mMessageHandler = new VideoProvider.VideoProviderHandler();
+            mMessageHandler = new VideoProvider.VideoProviderHandler(Looper.getMainLooper());
         }
 
         /**
@@ -763,11 +772,12 @@
          */
         public void receiveSessionModifyRequest(VideoProfile videoProfile) {
             if (mVideoCallbacks != null) {
-                try {
-                    for (IVideoCallback callback : mVideoCallbacks.values()) {
+                for (IVideoCallback callback : mVideoCallbacks.values()) {
+                    try {
                         callback.receiveSessionModifyRequest(videoProfile);
+                    } catch (RemoteException ignored) {
+                        Log.w(this, "receiveSessionModifyRequest callback failed", ignored);
                     }
-                } catch (RemoteException ignored) {
                 }
             }
         }
@@ -793,12 +803,13 @@
         public void receiveSessionModifyResponse(int status,
                 VideoProfile requestedProfile, VideoProfile responseProfile) {
             if (mVideoCallbacks != null) {
-                try {
-                    for (IVideoCallback callback : mVideoCallbacks.values()) {
+                for (IVideoCallback callback : mVideoCallbacks.values()) {
+                    try {
                         callback.receiveSessionModifyResponse(status, requestedProfile,
                                 responseProfile);
+                    } catch (RemoteException ignored) {
+                        Log.w(this, "receiveSessionModifyResponse callback failed", ignored);
                     }
-                } catch (RemoteException ignored) {
                 }
             }
         }
@@ -819,11 +830,12 @@
          */
         public void handleCallSessionEvent(int event) {
             if (mVideoCallbacks != null) {
-                try {
-                    for (IVideoCallback callback : mVideoCallbacks.values()) {
+                for (IVideoCallback callback : mVideoCallbacks.values()) {
+                    try {
                         callback.handleCallSessionEvent(event);
+                    } catch (RemoteException ignored) {
+                        Log.w(this, "handleCallSessionEvent callback failed", ignored);
                     }
-                } catch (RemoteException ignored) {
                 }
             }
         }
@@ -843,11 +855,12 @@
          */
         public void changePeerDimensions(int width, int height) {
             if (mVideoCallbacks != null) {
-                try {
-                    for (IVideoCallback callback : mVideoCallbacks.values()) {
+                for (IVideoCallback callback : mVideoCallbacks.values()) {
+                    try {
                         callback.changePeerDimensions(width, height);
+                    } catch (RemoteException ignored) {
+                        Log.w(this, "changePeerDimensions callback failed", ignored);
                     }
-                } catch (RemoteException ignored) {
                 }
             }
         }
@@ -869,11 +882,12 @@
          */
         public void setCallDataUsage(long dataUsage) {
             if (mVideoCallbacks != null) {
-                try {
-                    for (IVideoCallback callback : mVideoCallbacks.values()) {
+                for (IVideoCallback callback : mVideoCallbacks.values()) {
+                    try {
                         callback.changeCallDataUsage(dataUsage);
+                    } catch (RemoteException ignored) {
+                        Log.w(this, "setCallDataUsage callback failed", ignored);
                     }
-                } catch (RemoteException ignored) {
                 }
             }
         }
@@ -905,11 +919,12 @@
          */
         public void changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities) {
             if (mVideoCallbacks != null) {
-                try {
-                    for (IVideoCallback callback : mVideoCallbacks.values()) {
+                for (IVideoCallback callback : mVideoCallbacks.values()) {
+                    try {
                         callback.changeCameraCapabilities(cameraCapabilities);
+                    } catch (RemoteException ignored) {
+                        Log.w(this, "changeCameraCapabilities callback failed", ignored);
                     }
-                } catch (RemoteException ignored) {
                 }
             }
         }
@@ -929,11 +944,12 @@
          */
         public void changeVideoQuality(int videoQuality) {
             if (mVideoCallbacks != null) {
-                try {
-                    for (IVideoCallback callback : mVideoCallbacks.values()) {
+                for (IVideoCallback callback : mVideoCallbacks.values()) {
+                    try {
                         callback.changeVideoQuality(videoQuality);
+                    } catch (RemoteException ignored) {
+                        Log.w(this, "changeVideoQuality callback failed", ignored);
                     }
-                } catch (RemoteException ignored) {
                 }
             }
         }