Merge "Add a photos app filter and a metric for using it." into oc-mr1-dev
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 5dd47ac..9f1e983 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -232,8 +232,8 @@
     private class SurfaceCallback implements SurfaceHolder.Callback {
         @Override
         public void surfaceCreated(SurfaceHolder surfaceHolder) {
+            mSurface = mSurfaceView.getHolder().getSurface();
             if (mVirtualDisplay == null) {
-                mSurface = mSurfaceView.getHolder().getSurface();
                 initVirtualDisplay();
                 if (mVirtualDisplay != null && mActivityViewCallback != null) {
                     mActivityViewCallback.onActivityViewReady(ActivityView.this);
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 59fb269..df0e262 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -79,8 +79,9 @@
 
     private static final int EVENT_DB_CORRUPT = 75004;
 
+    // By default idle connections are not closed
     private static final boolean DEBUG_CLOSE_IDLE_CONNECTIONS = SystemProperties
-            .getBoolean("persist.debug.sqlite.close_idle_connections", true);
+            .getBoolean("persist.debug.sqlite.close_idle_connections", false);
 
     // Stores reference to all databases opened in the current process.
     // (The referent Object is not used at this time.)
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index fe9e8c6..da0ed54 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -149,14 +149,43 @@
      * provide a better experience than you could otherwise build using the generic building
      * blocks.
      *
+     * This will fallback to a generic pattern if one exists and there does not exist a
+     * hardware-specific implementation of the effect.
+     *
      * @param effectId The ID of the effect to perform:
-     * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}.
+     *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
      *
      * @return The desired effect.
      * @hide
      */
     public static VibrationEffect get(int effectId) {
-        VibrationEffect effect = new Prebaked(effectId);
+        return get(effectId, true);
+    }
+
+    /**
+     * Get a predefined vibration effect.
+     *
+     * Predefined effects are a set of common vibration effects that should be identical, regardless
+     * of the app they come from, in order to provide a cohesive experience for users across
+     * the entire device. They also may be custom tailored to the device hardware in order to
+     * provide a better experience than you could otherwise build using the generic building
+     * blocks.
+     *
+     * Some effects you may only want to play if there's a hardware specific implementation because
+     * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
+     * parameter allows you to decide whether you want to fallback to the generic implementation or
+     * only play if there's a tuned, hardware specific one available.
+     *
+     * @param effectId The ID of the effect to perform:
+     *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
+     * @param fallback Whether to fallback to a generic pattern if a hardware specific
+     *                 implementation doesn't exist.
+     *
+     * @return The desired effect.
+     * @hide
+     */
+    public static VibrationEffect get(int effectId, boolean fallback) {
+        VibrationEffect effect = new Prebaked(effectId, fallback);
         effect.validate();
         return effect;
     }
@@ -374,19 +403,29 @@
     /** @hide */
     public static class Prebaked extends VibrationEffect implements Parcelable {
         private int mEffectId;
+        private boolean mFallback;
 
         public Prebaked(Parcel in) {
-            this(in.readInt());
+            this(in.readInt(), in.readByte() != 0);
         }
 
-        public Prebaked(int effectId) {
+        public Prebaked(int effectId, boolean fallback) {
             mEffectId = effectId;
+            mFallback = fallback;
         }
 
         public int getId() {
             return mEffectId;
         }
 
+        /**
+         * Whether the effect should fall back to a generic pattern if there's no hardware specific
+         * implementation of it.
+         */
+        public boolean shouldFallback() {
+            return mFallback;
+        }
+
         @Override
         public void validate() {
             switch (mEffectId) {
@@ -406,7 +445,7 @@
                 return false;
             }
             VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
-            return mEffectId == other.mEffectId;
+            return mEffectId == other.mEffectId && mFallback == other.mFallback;
         }
 
         @Override
@@ -416,7 +455,7 @@
 
         @Override
         public String toString() {
-            return "Prebaked{mEffectId=" + mEffectId + "}";
+            return "Prebaked{mEffectId=" + mEffectId + ", mFallback=" + mFallback + "}";
         }
 
 
@@ -424,6 +463,7 @@
         public void writeToParcel(Parcel out, int flags) {
             out.writeInt(PARCEL_TOKEN_EFFECT);
             out.writeInt(mEffectId);
+            out.writeByte((byte) (mFallback ? 1 : 0));
         }
 
         public static final Parcelable.Creator<Prebaked> CREATOR =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ada7b2b..86c9246 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9332,6 +9332,25 @@
         public static final String ANOMALY_DETECTION_CONSTANTS = "anomaly_detection_constants";
 
         /**
+         * Always on display(AOD) specific settings
+         * This is encoded as a key=value list, separated by commas. Ex:
+         *
+         * "prox_screen_off_delay=10000,screen_brightness_array=0:1:2:3:4"
+         *
+         * The following keys are supported:
+         *
+         * <pre>
+         * screen_brightness_array         (string)
+         * dimming_scrim_array             (string)
+         * prox_screen_off_delay           (long)
+         * prox_cooldown_trigger           (long)
+         * prox_cooldown_period            (long)
+         * </pre>
+         * @hide
+         */
+        public static final String ALWAYS_ON_DISPLAY_CONSTANTS = "always_on_display_constants";
+
+        /**
          * App standby (app idle) specific settings.
          * This is encoded as a key=value list, separated by commas. Ex:
          *
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index b6a9a26..3b09c67 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -302,7 +302,7 @@
         // TODO: create a dump() method instead
         return new StringBuilder(
                 "FillResponse : [mRequestId=" + mRequestId)
-                .append(", datasets=").append(mDatasets)
+                .append(", datasets=").append(mDatasets == null ? "N/A" : mDatasets.getList())
                 .append(", saveInfo=").append(mSaveInfo)
                 .append(", clientState=").append(mClientState != null)
                 .append(", hasPresentation=").append(mPresentation != null)
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 80f4b99..ceb06f5 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1941,7 +1941,8 @@
             final int checkedPos = mAdapterView.getCheckedItemPosition();
             final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
             if (!useLayoutWithDefault()
-                    && (!hasValidSelection || mLastSelected != checkedPos)) {
+                    && (!hasValidSelection || mLastSelected != checkedPos)
+                    && mAlwaysButton != null) {
                 setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
                 mOnceButton.setEnabled(hasValidSelection);
                 if (hasValidSelection) {
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 17a598a..4b80a5f 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -28,7 +28,6 @@
 import android.graphics.Point;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.FileObserver;
@@ -74,7 +73,6 @@
 
     private Handler mHandler;
 
-    private static final String MIMETYPE_PDF = "application/pdf";
 
     private static final String MIMETYPE_JPEG = "image/jpeg";
 
@@ -425,13 +423,6 @@
             String documentId, Point sizeHint, CancellationSignal signal)
             throws FileNotFoundException {
         final File file = getFileForDocId(documentId);
-        if (getTypeForFile(file).equals(MIMETYPE_PDF)) {
-            try {
-                return PdfUtils.openPdfThumbnail(file, sizeHint);
-            } catch (Exception e) {
-                Log.v(TAG, "Could not load PDF's thumbnail", e);
-            }
-        }
         return DocumentsContract.openImageThumbnail(file);
     }
 
@@ -461,10 +452,7 @@
 
         final String mimeType = getTypeForFile(file);
         final String displayName = file.getName();
-        // As of right now, we aren't sure on the performance affect of loading all PDF Thumbnails
-        // Until a solution is found, it will be behind a debuggable flag.
-        if (mimeType.startsWith("image/")
-                || (mimeType.equals(MIMETYPE_PDF) && Build.IS_DEBUGGABLE)) {
+        if (mimeType.startsWith("image/")) {
             flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
         }
 
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 60fbbe9..0d962eb 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1812,7 +1812,7 @@
             mFloatingActionMode.finish();
         }
         cleanupFloatingActionModeViews();
-        mFloatingToolbar = new FloatingToolbar(mContext, mWindow);
+        mFloatingToolbar = new FloatingToolbar(mWindow);
         final FloatingActionMode mode =
                 new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar);
         mFloatingActionModeOriginatingView = originatingView;
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index 1d56e1a..f63b5a2 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -120,8 +120,10 @@
     /**
      * Initializes a floating toolbar.
      */
-    public FloatingToolbar(Context context, Window window) {
-        mContext = applyDefaultTheme(Preconditions.checkNotNull(context));
+    public FloatingToolbar(Window window) {
+        // TODO(b/65172902): Pass context in constructor when DecorView (and other callers)
+        // supports multi-display.
+        mContext = applyDefaultTheme(window.getContext());
         mWindow = Preconditions.checkNotNull(window);
         mPopup = new FloatingToolbarPopup(mContext, window.getDecorView());
     }
diff --git a/core/res/res/drawable/ic_corp_icon.xml b/core/res/res/drawable/ic_corp_icon.xml
index a6b68f1..48531dd 100644
--- a/core/res/res/drawable/ic_corp_icon.xml
+++ b/core/res/res/drawable/ic_corp_icon.xml
@@ -1,12 +1,9 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         android:width="48dp"
         android:height="48dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
     <path
-        android:pathData="M24,24m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"
-        android:fillColor="#FF6D00"/>
-    <path
-        android:pathData="M35.2,15.6h-5.6v-2.8c0,-1.55 -1.25,-2.8 -2.8,-2.8h-5.6c-1.55,0 -2.8,1.25 -2.8,2.8v2.8h-5.6c-1.55,0 -2.79,1.25 -2.79,2.8L10,33.8c0,1.55 1.25,2.8 2.8,2.8h22.4c1.55,0 2.8,-1.25 2.8,-2.8V18.4C38,16.85 36.75,15.6 35.2,15.6zM24,28.2c-1.54,0 -2.8,-1.26 -2.8,-2.8s1.26,-2.8 2.8,-2.8c1.54,0 2.8,1.26 2.8,2.8S25.54,28.2 24,28.2zM26.8,15.6h-5.6v-2.8h5.6V15.6z"
-        android:fillColor="#FFFFFF"/>
+        android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM12,15c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM14,6h-4L10,4h4v2z"
+        android:fillColor="#000000"/>
 </vector>
\ No newline at end of file
diff --git a/core/res/res/values-mcc505-mnc01/config.xml b/core/res/res/values-mcc505-mnc01/config.xml
index ff06585..5a5b8f7 100644
--- a/core/res/res/values-mcc505-mnc01/config.xml
+++ b/core/res/res/values-mcc505-mnc01/config.xml
@@ -31,15 +31,6 @@
       <item>9</item>
     </integer-array>
 
-    <!-- String containing the apn value for tethering.  May be overriden by secure settings
-         TETHER_DUN_APN.  Value is a comma separated series of strings:
-         "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type",
-         Or string format of ApnSettingV3.
-         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string-array translatable="false" name="config_tether_apndata">
-      <item>Telstra Tethering,telstra.internet,,,,,,,,,505,01,,DUN</item>
-    </string-array>
-
     <!--Thresholds for LTE dbm in status bar-->
     <integer-array translatable="false" name="config_lteDbmThresholds">
         <item>-140</item>    <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 24a4bae..6756e34 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -99,6 +99,7 @@
                     Settings.Global.ALARM_MANAGER_CONSTANTS,
                     Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED,
                     Settings.Global.ALWAYS_FINISH_ACTIVITIES,
+                    Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS,
                     Settings.Global.ANIMATOR_DURATION_SCALE,
                     Settings.Global.ANOMALY_DETECTION_CONSTANTS,
                     Settings.Global.APN_DB_UPDATE_CONTENT_URL,
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 4e59baa..3c3b317 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -216,10 +216,7 @@
             .setTransform(Matrix4::identity(), TransformFlags::None)
             .setModelViewIdentityEmptyBounds()
             .build();
-    // Disable blending if this is the first draw to the main framebuffer, in case app has defined
-    // transparency where it doesn't make sense - as first draw in opaque window.
-    bool overrideDisableBlending = !mHasDrawn && mOpaque && !mRenderTarget.frameBufferId;
-    mRenderState.render(glop, mRenderTarget.orthoMatrix, overrideDisableBlending);
+    mRenderState.render(glop, mRenderTarget.orthoMatrix, false);
     mHasDrawn = true;
 }
 
@@ -350,8 +347,14 @@
         const Glop& glop) {
     prepareRender(dirtyBounds, clip);
     // Disable blending if this is the first draw to the main framebuffer, in case app has defined
-    // transparency where it doesn't make sense - as first draw in opaque window.
-    bool overrideDisableBlending = !mHasDrawn && mOpaque && !mRenderTarget.frameBufferId;
+    // transparency where it doesn't make sense - as first draw in opaque window. Note that we only
+    // apply this improvement when the blend mode is SRC_OVER - other modes (e.g. CLEAR) can be
+    // valid draws that affect other content (e.g. draw CLEAR, then draw DST_OVER)
+    bool overrideDisableBlending = !mHasDrawn
+        && mOpaque
+        && !mRenderTarget.frameBufferId
+        && glop.blend.src == GL_ONE
+        && glop.blend.dst == GL_ONE_MINUS_SRC_ALPHA;
     mRenderState.render(glop, mRenderTarget.orthoMatrix, overrideDisableBlending);
     if (!mRenderTarget.frameBufferId) mHasDrawn = true;
 }
diff --git a/libs/hwui/renderstate/Blend.h b/libs/hwui/renderstate/Blend.h
index ec0e114..a9de246 100644
--- a/libs/hwui/renderstate/Blend.h
+++ b/libs/hwui/renderstate/Blend.h
@@ -40,6 +40,14 @@
             GLenum* outSrc, GLenum* outDst);
     void setFactors(GLenum src, GLenum dst);
 
+    bool  getEnabled() {
+        return mEnabled;
+    }
+    void getFactors(GLenum* src, GLenum* dst) {
+        *src = mSrcMode;
+        *dst = mDstMode;
+    }
+
     void dump();
 private:
     Blend();
diff --git a/libs/hwui/tests/unit/BakedOpRendererTests.cpp b/libs/hwui/tests/unit/BakedOpRendererTests.cpp
index 603599c..38e106a 100644
--- a/libs/hwui/tests/unit/BakedOpRendererTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpRendererTests.cpp
@@ -17,6 +17,7 @@
 #include <gtest/gtest.h>
 
 #include <BakedOpRenderer.h>
+#include <GlopBuilder.h>
 #include <tests/common/TestUtils.h>
 
 using namespace android::uirenderer;
@@ -53,3 +54,53 @@
         renderer.endLayer();
     }
 }
+
+static void drawFirstOp(RenderState& renderState, int color, SkBlendMode mode) {
+    BakedOpRenderer renderer(Caches::getInstance(), renderState, true, false, sLightInfo);
+
+    renderer.startFrame(100, 100, Rect(100, 100));
+    SkPaint paint;
+    paint.setColor(color);
+    paint.setBlendMode(mode);
+
+    Rect dest(0, 0, 100, 100);
+    Glop glop;
+    GlopBuilder(renderState, Caches::getInstance(), &glop)
+            .setRoundRectClipState(nullptr)
+            .setMeshUnitQuad()
+            .setFillPaint(paint, 1.0f)
+            .setTransform(Matrix4::identity(), TransformFlags::None)
+            .setModelViewMapUnitToRectSnap(dest)
+            .build();
+    renderer.renderGlop(nullptr, nullptr, glop);
+    renderer.endFrame(Rect(100, 100));
+}
+
+static void verifyBlend(RenderState& renderState, GLenum expectedSrc, GLenum expectedDst) {
+    EXPECT_TRUE(renderState.blend().getEnabled());
+    GLenum src;
+    GLenum dst;
+    renderState.blend().getFactors(&src, &dst);
+    EXPECT_EQ(expectedSrc, src);
+    EXPECT_EQ(expectedDst, dst);
+}
+
+static void verifyBlendDisabled(RenderState& renderState) {
+    EXPECT_FALSE(renderState.blend().getEnabled());
+}
+
+RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, firstDrawBlend_clear) {
+    // initialize blend state to nonsense value
+    renderThread.renderState().blend().setFactors(GL_ONE, GL_ONE);
+
+    drawFirstOp(renderThread.renderState(), 0xfeff0000, SkBlendMode::kClear);
+    verifyBlend(renderThread.renderState(), GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
+}
+
+RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, firstDrawBlend_srcover) {
+    // initialize blend state to nonsense value
+    renderThread.renderState().blend().setFactors(GL_ONE, GL_ONE);
+
+    drawFirstOp(renderThread.renderState(), 0xfeff0000, SkBlendMode::kSrcOver);
+    verifyBlendDisabled(renderThread.renderState());
+}
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 3308fc9..dc7fa8c 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -28,6 +28,7 @@
 
     MediaRouterClientState getState(IMediaRouterClient client);
     boolean isPlaybackActive(IMediaRouterClient client);
+    boolean isGlobalBluetoothA2doOn();
 
     void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan);
     void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit);
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 29b88a2..2894e89 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -88,7 +88,6 @@
         RouteInfo mBluetoothA2dpRoute;
 
         RouteInfo mSelectedRoute;
-        RouteInfo mSystemAudioRoute;
 
         final boolean mCanConfigureWifiDisplays;
         boolean mActivelyScanningWifiDisplays;
@@ -150,7 +149,6 @@
             }
 
             addRouteStatic(mDefaultAudioVideo);
-            mSystemAudioRoute = mDefaultAudioVideo;
 
             // This will select the active wifi display route if there is one.
             updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus());
@@ -185,7 +183,7 @@
         }
 
         void updateAudioRoutes(AudioRoutesInfo newRoutes) {
-            boolean updated = false;
+            boolean audioRoutesChanged = false;
             if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) {
                 mCurAudioRoutesInfo.mainType = newRoutes.mainType;
                 int name;
@@ -201,11 +199,10 @@
                 }
                 mDefaultAudioVideo.mNameResId = name;
                 dispatchRouteChanged(mDefaultAudioVideo);
-                updated = true;
+                audioRoutesChanged = true;
             }
 
             final int mainType = mCurAudioRoutesInfo.mainType;
-
             if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
                 mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
                 if (mCurAudioRoutesInfo.bluetoothName != null) {
@@ -219,8 +216,6 @@
                         info.mDeviceType = RouteInfo.DEVICE_TYPE_BLUETOOTH;
                         mBluetoothA2dpRoute = info;
                         addRouteStatic(mBluetoothA2dpRoute);
-                        mSystemAudioRoute = mBluetoothA2dpRoute;
-                        selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mSystemAudioRoute, false);
                     } else {
                         mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.bluetoothName;
                         dispatchRouteChanged(mBluetoothA2dpRoute);
@@ -229,30 +224,32 @@
                     // BT disconnected
                     removeRouteStatic(mBluetoothA2dpRoute);
                     mBluetoothA2dpRoute = null;
-                    mSystemAudioRoute = mDefaultAudioVideo;
-                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mSystemAudioRoute, false);
                 }
-                updated = true;
+                audioRoutesChanged = true;
             }
 
-            if (mBluetoothA2dpRoute != null) {
-                final boolean a2dpEnabled = isBluetoothA2dpOn();
-                if (mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) {
-                    // A2DP off
-                    mSystemAudioRoute = mDefaultAudioVideo;
-                    updated = true;
-                } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) &&
-                        a2dpEnabled) {
-                    // A2DP on or BT connected
-                    mSystemAudioRoute = mBluetoothA2dpRoute;
-                    updated = true;
-                }
-            }
-            if (updated) {
+            if (audioRoutesChanged) {
+                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, getDefaultSystemAudioRoute(), false);
                 Log.v(TAG, "Audio routes updated: " + newRoutes + ", a2dp=" + isBluetoothA2dpOn());
             }
         }
 
+        RouteInfo getDefaultSystemAudioRoute() {
+            boolean globalBluetoothA2doOn = false;
+            try {
+                globalBluetoothA2doOn = mMediaRouterService.isGlobalBluetoothA2doOn();
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Unable to call isSystemBluetoothA2doOn.", ex);
+            }
+            return (globalBluetoothA2doOn && mBluetoothA2dpRoute != null)
+                    ? mBluetoothA2dpRoute : mDefaultAudioVideo;
+        }
+
+        RouteInfo getCurrentSystemAudioRoute() {
+            return (isBluetoothA2dpOn() && mBluetoothA2dpRoute != null)
+                    ? mBluetoothA2dpRoute : mDefaultAudioVideo;
+        }
+
         boolean isBluetoothA2dpOn() {
             try {
                 return mAudioService.isBluetoothA2dpOn();
@@ -603,15 +600,13 @@
 
             @Override
             public void onRestoreRoute() {
+                // Skip restoring route if the selected route is not a system audio route, or
+                // MediaRouter is initializing.
                 if ((mSelectedRoute != mDefaultAudioVideo && mSelectedRoute != mBluetoothA2dpRoute)
-                        || mSelectedRoute == mSystemAudioRoute) {
+                        || mSelectedRoute == null) {
                     return;
                 }
-                try {
-                    sStatic.mAudioService.setBluetoothA2dpOn(mSelectedRoute == mBluetoothA2dpRoute);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error changing Bluetooth A2DP state", e);
-                }
+                mSelectedRoute.select();
             }
         }
     }
@@ -946,7 +941,7 @@
         boolean wasDefaultOrBluetoothRoute = (oldRoute == sStatic.mDefaultAudioVideo
                 || oldRoute == sStatic.mBluetoothA2dpRoute);
         if (oldRoute == route
-                && (!wasDefaultOrBluetoothRoute || oldRoute == sStatic.mSystemAudioRoute)) {
+                && (!wasDefaultOrBluetoothRoute || route == sStatic.getCurrentSystemAudioRoute())) {
             return;
         }
         if (!route.matchesTypes(types)) {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index b0052cc..a61881f 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -82,6 +82,7 @@
     private MyWebViewClient mWebViewClient;
     private boolean mLaunchBrowser = false;
     private Thread mTestingThread = null;
+    private boolean mReload = false;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -290,9 +291,13 @@
                 mCm.bindProcessToNetwork(network);
                 mNetwork = network;
                 runOnUiThreadIfNotFinishing(() -> {
-                    // Start initial page load so WebView finishes loading proxy settings.
-                    // Actual load of mUrl is initiated by MyWebViewClient.
-                    mWebView.loadData("", "text/html", null);
+                    if (mReload) {
+                        mWebView.reload();
+                    } else {
+                        // Start initial page load so WebView finishes loading proxy settings.
+                        // Actual load of mUrl is initiated by MyWebViewClient.
+                        mWebView.loadData("", "text/html", null);
+                    }
                 });
             }
 
@@ -305,6 +310,12 @@
                     mWebView.loadUrl(mUrl.toString());
                 });
             }
+
+            @Override
+            public void onLost(Network lostNetwork) {
+                if (DBG) logd("Network lost");
+                mReload = true;
+            }
         };
         logd("request Network for captive portal");
         mCm.requestNetwork(request, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MS);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
index a2bf964..33cb5964 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -27,6 +27,7 @@
 import android.os.Bundle;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Log;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.carrierdefaultapp.R;
@@ -199,13 +200,19 @@
                                          PendingIntent pendingIntent) {
         final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
         final Resources resources = context.getResources();
+        String spn = telephonyMgr.getSimOperatorName();
+        if (TextUtils.isEmpty(spn)) {
+            // There is no consistent way to get the current carrier name as MNOs didn't
+            // bother to set EF_SPN. in the long term, we should display a generic wording if
+            // spn from subscription is not set.
+            spn = telephonyMgr.getNetworkOperatorName();
+        }
         final Bundle extras = Bundle.forPair(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                 resources.getString(R.string.android_system_label));
         createNotificationChannels(context);
         Notification.Builder builder = new Notification.Builder(context)
                 .setContentTitle(resources.getString(titleId))
-                .setContentText(String.format(resources.getString(textId),
-                        telephonyMgr.getNetworkOperatorName()))
+                .setContentText(String.format(resources.getString(textId), spn))
                 .setSmallIcon(R.drawable.ic_sim_card)
                 .setColor(context.getColor(
                         com.android.internal.R.color.system_notification_accent_color))
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 0bf2eda..77df02b 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -105,6 +105,7 @@
     <!-- Titles for Bluetooth AVRCP Versions -->
     <string-array name="bluetooth_avrcp_versions">
         <item>AVRCP 1.4 (Default)</item>
+        <item>AVRCP 1.3</item>
         <item>AVRCP 1.5</item>
         <item>AVRCP 1.6</item>
     </string-array>
@@ -112,6 +113,7 @@
     <!-- Values for Bluetooth AVRCP Versions -->
     <string-array name="bluetooth_avrcp_version_values">
         <item>avrcp14</item>
+        <item>avrcp13</item>
         <item>avrcp15</item>
         <item>avrcp16</item>
     </string-array>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 0a53c78..64ec16a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -99,7 +99,9 @@
     public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
         final int iconSize = UserIconDrawable.getSizeForList(context);
         if (user.isManagedProfile()) {
-            return context.getDrawable(com.android.internal.R.drawable.ic_corp_icon);
+            Drawable drawable = context.getDrawable(com.android.internal.R.drawable.ic_corp_icon);
+            drawable.setBounds(0, 0, iconSize, iconSize);
+            return drawable;
         }
         if (user.iconPath != null) {
             Bitmap icon = um.getUserIcon(user.id);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 2c4f9c4..c24cdae 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -76,10 +76,10 @@
     public static String generatePreferenceKey(AccessPoint accessPoint) {
         StringBuilder builder = new StringBuilder();
 
-        if (TextUtils.isEmpty(accessPoint.getBssid())) {
-            builder.append(accessPoint.getSsidStr());
-        } else {
+        if (TextUtils.isEmpty(accessPoint.getSsidStr())) {
             builder.append(accessPoint.getBssid());
+        } else {
+            builder.append(accessPoint.getSsidStr());
         }
 
         builder.append(',').append(accessPoint.getSecurity());
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
index 7fe69a7..6cfdc28 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
@@ -36,26 +36,28 @@
     private Context mContext = RuntimeEnvironment.application;
 
     @Test
-    public void generatePreferenceKey_shouldReturnSsidPlusSecurity() {
+    public void generatePreferenceKey_returnsSsidPlusSecurity() {
         String ssid = "ssid";
+        String bssid = "00:00:00:00:00:00";
         int security = AccessPoint.SECURITY_WEP;
         String expectedKey = ssid + ',' + security;
 
         TestAccessPointBuilder builder = new TestAccessPointBuilder(mContext);
-        builder.setSsid(ssid).setSecurity(security);
+        builder.setBssid(bssid).setSsid(ssid).setSecurity(security);
 
         assertThat(AccessPointPreference.generatePreferenceKey(builder.build()))
                 .isEqualTo(expectedKey);
     }
 
     @Test
-    public void generatePreferenceKey_shouldReturnBssidPlusSecurity() {
-        String bssid = "bssid";
+    public void generatePreferenceKey_emptySsidReturnsBssidPlusSecurity() {
+        String ssid = "";
+        String bssid = "00:00:00:00:00:00";
         int security = AccessPoint.SECURITY_WEP;
         String expectedKey = bssid + ',' + security;
 
         TestAccessPointBuilder builder = new TestAccessPointBuilder(mContext);
-        builder.setBssid(bssid).setSecurity(security);
+        builder.setBssid(bssid).setSsid(ssid).setSecurity(security);
 
         assertThat(AccessPointPreference.generatePreferenceKey(builder.build()))
                 .isEqualTo(expectedKey);
diff --git a/packages/SystemUI/res/drawable/car_qs_background_primary.xml b/packages/SystemUI/res/drawable/car_qs_background_primary.xml
deleted file mode 100644
index 0f77987..0000000
--- a/packages/SystemUI/res/drawable/car_qs_background_primary.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<inset xmlns:android="http://schemas.android.com/apk/res/android">
-    <shape>
-        <solid android:color="?android:attr/colorPrimaryDark"/>
-    </shape>
-</inset>
diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml
index deb494f..59c0957 100644
--- a/packages/SystemUI/res/layout/battery_percentage_view.xml
+++ b/packages/SystemUI/res/layout/battery_percentage_view.xml
@@ -26,4 +26,5 @@
         android:textColor="?android:attr/textColorPrimary"
         android:gravity="center_vertical|start"
         android:paddingEnd="@dimen/battery_level_padding_start"
+        android:importantForAccessibility="no"
         />
diff --git a/packages/SystemUI/res/layout/car_qs_panel.xml b/packages/SystemUI/res/layout/car_qs_panel.xml
index 0b46b0b..4cb0fd5 100644
--- a/packages/SystemUI/res/layout/car_qs_panel.xml
+++ b/packages/SystemUI/res/layout/car_qs_panel.xml
@@ -18,12 +18,13 @@
     android:id="@+id/quick_settings_container"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="@drawable/car_qs_background_primary"
+    android:background="@color/car_qs_background_primary"
     android:orientation="vertical"
-    android:elevation="4dp">
+    android:elevation="4dp"
+    android:theme="@android:style/Theme">
 
-    <include layout="@layout/car_status_bar_header" />
-    <include layout="@layout/car_qs_footer" />
+    <include layout="@layout/car_status_bar_header"/>
+    <include layout="@layout/car_qs_footer"/>
 
     <com.android.systemui.statusbar.car.UserGridView
         android:id="@+id/user_grid"
diff --git a/packages/SystemUI/res/layout/home.xml b/packages/SystemUI/res/layout/home.xml
index 53ef2ab..7b67b79 100644
--- a/packages/SystemUI/res/layout/home.xml
+++ b/packages/SystemUI/res/layout/home.xml
@@ -23,8 +23,8 @@
     systemui:keyCode="3"
     android:scaleType="fitCenter"
     android:contentDescription="@string/accessibility_home"
-    android:paddingTop="13dp"
-    android:paddingBottom="13dp"
+    android:paddingTop="@dimen/home_padding"
+    android:paddingBottom="@dimen/home_padding"
     android:paddingStart="@dimen/navigation_key_padding"
     android:paddingEnd="@dimen/navigation_key_padding"
     />
diff --git a/packages/SystemUI/res/values/colors_car.xml b/packages/SystemUI/res/values/colors_car.xml
index 9593fe5..1b8c2fa 100644
--- a/packages/SystemUI/res/values/colors_car.xml
+++ b/packages/SystemUI/res/values/colors_car.xml
@@ -17,6 +17,7 @@
  */
 -->
 <resources>
+    <color name="car_qs_background_primary">#263238</color> <!-- Blue Gray 900 -->
     <color name="car_user_switcher_progress_bgcolor">#00000000</color> <!-- Transparent -->
     <color name="car_user_switcher_progress_fgcolor">#80CBC4</color> <!-- Teal 200 -->
     <color name="car_user_switcher_no_user_image_bgcolor">#FAFAFA</color> <!-- Grey 50 -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 54421f74..bcaafcc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -854,4 +854,7 @@
     <!-- How far to inset the rounded edges -->
     <dimen name="stat_sys_mobile_signal_circle_inset">0.9dp</dimen>
 
+    <!-- Home button padding for sizing -->
+    <dimen name="home_padding">15dp</dimen>
+
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
new file mode 100644
index 0000000..d1d1808
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.doze;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.Settings;
+import android.text.format.DateUtils;
+import android.util.KeyValueListParser;
+import android.util.Log;
+
+import com.android.systemui.R;
+
+import java.util.Arrays;
+
+/**
+ * Class to store the policy for AOD, which comes from
+ * {@link android.provider.Settings.Global}
+ */
+public class AlwaysOnDisplayPolicy {
+    public static final String TAG = "AlwaysOnDisplayPolicy";
+
+    static final String KEY_SCREEN_BRIGHTNESS_ARRAY = "screen_brightness_array";
+    static final String KEY_DIMMING_SCRIM_ARRAY = "dimming_scrim_array";
+    static final String KEY_PROX_SCREEN_OFF_DELAY_MS = "prox_screen_off_delay";
+    static final String KEY_PROX_COOLDOWN_TRIGGER_MS = "prox_cooldown_trigger";
+    static final String KEY_PROX_COOLDOWN_PERIOD_MS = "prox_cooldown_period";
+
+    /**
+     * Integer array to map ambient brightness type to real screen brightness.
+     *
+     * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+     * @see #KEY_SCREEN_BRIGHTNESS_ARRAY
+     */
+    public final int[] screenBrightnessArray;
+
+    /**
+     * Integer array to map ambient brightness type to dimming scrim.
+     *
+     * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+     * @see #KEY_DIMMING_SCRIM_ARRAY
+     */
+    public final int[] dimmingScrimArray;
+
+    /**
+     * Delay time(ms) from covering the prox to turning off the screen.
+     *
+     * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+     * @see #KEY_PROX_SCREEN_OFF_DELAY_MS
+     */
+    public final long proxScreenOffDelayMs;
+
+    /**
+     * The threshold time(ms) to trigger the cooldown timer, which will
+     * turn off prox sensor for a period.
+     *
+     * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+     * @see #KEY_PROX_COOLDOWN_TRIGGER_MS
+     */
+    public final long proxCooldownTriggerMs;
+
+    /**
+     * The period(ms) to turning off the prox sensor if
+     * {@link #KEY_PROX_COOLDOWN_TRIGGER_MS} is triggered.
+     *
+     * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
+     * @see #KEY_PROX_COOLDOWN_PERIOD_MS
+     */
+    public final long proxCooldownPeriodMs;
+
+    private final KeyValueListParser mParser;
+
+    public AlwaysOnDisplayPolicy(Context context) {
+        final Resources resources = context.getResources();
+        mParser = new KeyValueListParser(',');
+
+        final String value = Settings.Global.getString(context.getContentResolver(),
+                Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
+
+        try {
+            mParser.setString(value);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Bad AOD constants");
+        }
+
+        proxScreenOffDelayMs = mParser.getLong(KEY_PROX_SCREEN_OFF_DELAY_MS,
+                10 * DateUtils.MINUTE_IN_MILLIS);
+        proxCooldownTriggerMs = mParser.getLong(KEY_PROX_COOLDOWN_TRIGGER_MS,
+                2 * DateUtils.MINUTE_IN_MILLIS);
+        proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
+                5 * DateUtils.MINUTE_IN_MILLIS);
+        screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
+                resources.getIntArray(R.array.config_doze_brightness_sensor_to_brightness));
+        dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY,
+                resources.getIntArray(R.array.config_doze_brightness_sensor_to_scrim_opacity));
+    }
+
+    private int[] parseIntArray(final String key, final int[] defaultArray) {
+        final String value = mParser.getString(key, null);
+        if (value != null) {
+            return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
+                    Integer::parseInt).toArray();
+        } else {
+            return defaultArray;
+        }
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index d374d68..6f8bcff 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -59,7 +59,7 @@
 
         DozeMachine machine = new DozeMachine(wrappedService, config, wakeLock);
         machine.setParts(new DozeMachine.Part[]{
-                new DozePauser(handler, machine, alarmManager),
+                new DozePauser(handler, machine, alarmManager, new AlwaysOnDisplayPolicy(context)),
                 new DozeFalsingManagerAdapter(FalsingManager.getInstance(context)),
                 createDozeTriggers(context, sensorManager, host, alarmManager, config, params,
                         handler, wakeLock, machine),
@@ -76,7 +76,8 @@
             Handler handler) {
         Sensor sensor = DozeSensors.findSensorWithType(sensorManager,
                 context.getString(R.string.doze_brightness_sensor_type));
-        return new DozeScreenBrightness(context, service, sensorManager, sensor, host, handler);
+        return new DozeScreenBrightness(context, service, sensorManager, sensor, host, handler,
+                new AlwaysOnDisplayPolicy(context));
     }
 
     private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java b/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java
index a33b454c..76a1902 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java
@@ -26,20 +26,22 @@
  */
 public class DozePauser implements DozeMachine.Part {
     public static final String TAG = DozePauser.class.getSimpleName();
-    private static final long TIMEOUT = 10 * 1000;
     private final AlarmTimeout mPauseTimeout;
     private final DozeMachine mMachine;
+    private final long mTimeoutMs;
 
-    public DozePauser(Handler handler, DozeMachine machine, AlarmManager alarmManager) {
+    public DozePauser(Handler handler, DozeMachine machine, AlarmManager alarmManager,
+            AlwaysOnDisplayPolicy policy) {
         mMachine = machine;
         mPauseTimeout = new AlarmTimeout(alarmManager, this::onTimeout, TAG, handler);
+        mTimeoutMs = policy.proxScreenOffDelayMs;
     }
 
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         switch (newState) {
             case DOZE_AOD_PAUSING:
-                mPauseTimeout.schedule(TIMEOUT, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+                mPauseTimeout.schedule(mTimeoutMs, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
                 break;
             default:
                 mPauseTimeout.cancel();
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 30420529..11b4b0ef 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -42,7 +42,7 @@
 
     public DozeScreenBrightness(Context context, DozeMachine.Service service,
             SensorManager sensorManager, Sensor lightSensor, DozeHost host,
-            Handler handler) {
+            Handler handler, AlwaysOnDisplayPolicy policy) {
         mContext = context;
         mDozeService = service;
         mSensorManager = sensorManager;
@@ -50,10 +50,8 @@
         mDozeHost = host;
         mHandler = handler;
 
-        mSensorToBrightness = context.getResources().getIntArray(
-                R.array.config_doze_brightness_sensor_to_brightness);
-        mSensorToScrimOpacity = context.getResources().getIntArray(
-                R.array.config_doze_brightness_sensor_to_scrim_opacity);
+        mSensorToBrightness = policy.screenBrightnessArray;
+        mSensorToScrimOpacity = policy.dimmingScrimArray;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 566353c..91cde37 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -72,7 +72,7 @@
     public DozeSensors(Context context, AlarmManager alarmManager, SensorManager sensorManager,
             DozeParameters dozeParameters,
             AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback,
-            Consumer<Boolean> proxCallback) {
+            Consumer<Boolean> proxCallback, AlwaysOnDisplayPolicy policy) {
         mContext = context;
         mAlarmManager = alarmManager;
         mSensorManager = sensorManager;
@@ -112,7 +112,7 @@
                         true /* touchscreen */),
         };
 
-        mProxSensor = new ProxSensor();
+        mProxSensor = new ProxSensor(policy);
         mCallback = callback;
     }
 
@@ -206,17 +206,16 @@
 
     private class ProxSensor implements SensorEventListener {
 
-        static final long COOLDOWN_TRIGGER = 2 * 1000;
-        static final long COOLDOWN_PERIOD = 5 * 1000;
-
         boolean mRequested;
         boolean mRegistered;
         Boolean mCurrentlyFar;
         long mLastNear;
         final AlarmTimeout mCooldownTimer;
+        final AlwaysOnDisplayPolicy mPolicy;
 
 
-        public ProxSensor() {
+        public ProxSensor(AlwaysOnDisplayPolicy policy) {
+            mPolicy = policy;
             mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered,
                     "prox_cooldown", mHandler);
         }
@@ -264,11 +263,12 @@
                 // Sensor has been unregistered by the proxCallback. Do nothing.
             } else if (!mCurrentlyFar) {
                 mLastNear = now;
-            } else if (mCurrentlyFar && now - mLastNear < COOLDOWN_TRIGGER) {
+            } else if (mCurrentlyFar && now - mLastNear < mPolicy.proxCooldownTriggerMs) {
                 // If the last near was very recent, we might be using more power for prox
                 // wakeups than we're saving from turning of the screen. Instead, turn it off
                 // for a while.
-                mCooldownTimer.schedule(COOLDOWN_PERIOD, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+                mCooldownTimer.schedule(mPolicy.proxCooldownPeriodMs,
+                        AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
                 updateRegistered();
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 4583160..f7a258a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -84,7 +84,8 @@
         mWakeLock = wakeLock;
         mAllowPulseTriggers = allowPulseTriggers;
         mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters,
-                config, wakeLock, this::onSensor, this::onProximityFar);
+                config, wakeLock, this::onSensor, this::onProximityFar,
+                new AlwaysOnDisplayPolicy(context));
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 13ba7c1..901b0b0 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -19,6 +19,7 @@
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER;
+import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_WILL_RESIZE_MENU;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_DISMISS_FRACTION;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MOVEMENT_BOUNDS;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MENU_STATE;
@@ -132,7 +133,8 @@
                     showMenu(data.getInt(EXTRA_MENU_STATE),
                             data.getParcelable(EXTRA_STACK_BOUNDS),
                             data.getParcelable(EXTRA_MOVEMENT_BOUNDS),
-                            data.getBoolean(EXTRA_ALLOW_TIMEOUT));
+                            data.getBoolean(EXTRA_ALLOW_TIMEOUT),
+                            data.getBoolean(EXTRA_WILL_RESIZE_MENU));
                     break;
                 }
                 case MESSAGE_POKE_MENU:
@@ -307,12 +309,14 @@
     }
 
     private void showMenu(int menuState, Rect stackBounds, Rect movementBounds,
-            boolean allowMenuTimeout) {
+            boolean allowMenuTimeout, boolean resizeMenuOnShow) {
         mAllowMenuTimeout = allowMenuTimeout;
         if (mMenuState != menuState) {
-            boolean deferTouchesUntilAnimationEnds = (mMenuState == MENU_STATE_FULL) ||
-                    (menuState == MENU_STATE_FULL);
-            mAllowTouches = !deferTouchesUntilAnimationEnds;
+            // Disallow touches if the menu needs to resize while showing, and we are transitioning
+            // to/from a full menu state.
+            boolean disallowTouchesUntilAnimationEnd = resizeMenuOnShow &&
+                    (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL);
+            mAllowTouches = !disallowTouchesUntilAnimationEnd;
             cancelDelayedFinish();
             updateActionViews(stackBounds);
             if (mMenuContainerAnimator != null) {
@@ -409,7 +413,8 @@
             Rect stackBounds = intent.getParcelableExtra(EXTRA_STACK_BOUNDS);
             Rect movementBounds = intent.getParcelableExtra(EXTRA_MOVEMENT_BOUNDS);
             boolean allowMenuTimeout = intent.getBooleanExtra(EXTRA_ALLOW_TIMEOUT, true);
-            showMenu(menuState, stackBounds, movementBounds, allowMenuTimeout);
+            boolean willResizeMenu = intent.getBooleanExtra(EXTRA_WILL_RESIZE_MENU, false);
+            showMenu(menuState, stackBounds, movementBounds, allowMenuTimeout, willResizeMenu);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index c558056..e898a51 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -63,6 +63,7 @@
     public static final String EXTRA_STACK_BOUNDS = "stack_bounds";
     public static final String EXTRA_MOVEMENT_BOUNDS = "movement_bounds";
     public static final String EXTRA_ALLOW_TIMEOUT = "allow_timeout";
+    public static final String EXTRA_WILL_RESIZE_MENU = "resize_menu_on_show";
     public static final String EXTRA_DISMISS_FRACTION = "dismiss_fraction";
     public static final String EXTRA_MENU_STATE = "menu_state";
 
@@ -268,7 +269,8 @@
             // If we haven't requested the start activity, or if it previously took too long to
             // start, then start it
             startMenuActivity(MENU_STATE_NONE, null /* stackBounds */,
-                    null /* movementBounds */, false /* allowMenuTimeout */);
+                    null /* movementBounds */, false /* allowMenuTimeout */,
+                    false /* resizeMenuOnShow */);
         }
     }
 
@@ -276,18 +278,20 @@
      * Shows the menu activity.
      */
     public void showMenu(int menuState, Rect stackBounds, Rect movementBounds,
-            boolean allowMenuTimeout) {
+            boolean allowMenuTimeout, boolean willResizeMenu) {
         if (DEBUG) {
             Log.d(TAG, "showMenu() state=" + menuState
                     + " hasActivity=" + (mToActivityMessenger != null)
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
+
         if (mToActivityMessenger != null) {
             Bundle data = new Bundle();
             data.putInt(EXTRA_MENU_STATE, menuState);
             data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds);
             data.putParcelable(EXTRA_MOVEMENT_BOUNDS, movementBounds);
             data.putBoolean(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout);
+            data.putBoolean(EXTRA_WILL_RESIZE_MENU, willResizeMenu);
             Message m = Message.obtain();
             m.what = PipMenuActivity.MESSAGE_SHOW_MENU;
             m.obj = data;
@@ -299,7 +303,8 @@
         } else if (!mStartActivityRequested || isStartActivityRequestedElapsed()) {
             // If we haven't requested the start activity, or if it previously took too long to
             // start, then start it
-            startMenuActivity(menuState, stackBounds, movementBounds, allowMenuTimeout);
+            startMenuActivity(menuState, stackBounds, movementBounds, allowMenuTimeout,
+                    willResizeMenu);
         }
     }
 
@@ -372,7 +377,7 @@
      * Starts the menu activity on the top task of the pinned stack.
      */
     private void startMenuActivity(int menuState, Rect stackBounds, Rect movementBounds,
-            boolean allowMenuTimeout) {
+            boolean allowMenuTimeout, boolean willResizeMenu) {
         try {
             StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
             if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
@@ -388,6 +393,7 @@
                 }
                 intent.putExtra(EXTRA_MENU_STATE, menuState);
                 intent.putExtra(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout);
+                intent.putExtra(EXTRA_WILL_RESIZE_MENU, willResizeMenu);
                 ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
                 options.setLaunchTaskId(
                         pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 8ddd888..3181481 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -170,7 +170,7 @@
         @Override
         public void onPipShowMenu() {
             mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
-                    mMovementBounds, true /* allowMenuTimeout */);
+                    mMovementBounds, true /* allowMenuTimeout */, willResizeMenu());
         }
     }
 
@@ -214,7 +214,7 @@
         // Only show the menu if the user isn't currently interacting with the PiP
         if (!mTouchState.isUserInteracting()) {
             mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
-                    mMovementBounds, false /* allowMenuTimeout */);
+                    mMovementBounds, false /* allowMenuTimeout */, willResizeMenu());
         }
     }
 
@@ -236,7 +236,7 @@
 
         if (mShowPipMenuOnAnimationEnd) {
             mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(),
-                    mMovementBounds, true /* allowMenuTimeout */);
+                    mMovementBounds, true /* allowMenuTimeout */, false /* willResizeMenu */);
             mShowPipMenuOnAnimationEnd = false;
         }
     }
@@ -337,7 +337,7 @@
 
     private void onAccessibilityShowMenu() {
         mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
-                mMovementBounds, false /* allowMenuTimeout */);
+                mMovementBounds, false /* allowMenuTimeout */, willResizeMenu());
     }
 
     private boolean handleTouchEvent(MotionEvent ev) {
@@ -704,7 +704,7 @@
                     // If the menu is still visible, and we aren't minimized, then just poke the
                     // menu so that it will timeout after the user stops touching it
                     mMenuController.showMenu(mMenuState, mMotionHelper.getBounds(),
-                            mMovementBounds, true /* allowMenuTimeout */);
+                            mMovementBounds, true /* allowMenuTimeout */, willResizeMenu());
                 } else {
                     // If the menu is not visible, then we can still be showing the activity for the
                     // dismiss overlay, so just finish it after the animation completes
@@ -731,7 +731,7 @@
                 setMinimizedStateInternal(false);
             } else if (mMenuState != MENU_STATE_FULL) {
                 mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
-                        mMovementBounds, true /* allowMenuTimeout */);
+                        mMovementBounds, true /* allowMenuTimeout */, willResizeMenu());
             } else {
                 mMenuController.hideMenu();
                 mMotionHelper.expandPip();
@@ -773,6 +773,14 @@
         cleanUpDismissTarget();
     }
 
+    /**
+     * @return whether the menu will resize as a part of showing the full menu.
+     */
+    private boolean willResizeMenu() {
+        return mExpandedBounds.width() != mNormalBounds.width() ||
+                mExpandedBounds.height() != mNormalBounds.height();
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
index f663315..82c0128 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
@@ -136,11 +136,12 @@
         return disableAny;
     }
 
-    public void disableAll() {
+    public boolean disableAll() {
         ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins);
         for (int i = 0; i < plugins.size(); i++) {
             disable(plugins.get(i));
         }
+        return plugins.size() != 0;
     }
 
     private void disable(PluginInfo info) {
@@ -182,6 +183,7 @@
                     if (DEBUG) Log.d(TAG, "onPluginConnected");
                     PluginPrefs.setHasPlugins(mContext);
                     PluginInfo<T> info = (PluginInfo<T>) msg.obj;
+                    mManager.handleWtfs();
                     if (!(msg.obj instanceof PluginFragment)) {
                         // Only call onDestroy for plugins that aren't fragments, as fragments
                         // will get the onCreate as part of the fragment lifecycle.
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
index 493d244..03747d5 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
@@ -36,6 +36,9 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Log;
+import android.util.Log.TerribleFailure;
+import android.util.Log.TerribleFailureHandler;
 import android.widget.Toast;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -71,10 +74,11 @@
     private boolean mListening;
     private boolean mHasOneShot;
     private Looper mLooper;
+    private boolean mWtfsSet;
 
     public PluginManagerImpl(Context context) {
         this(context, new PluginInstanceManagerFactory(),
-                Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler());
+                Build.IS_DEBUGGABLE, Thread.getUncaughtExceptionPreHandler());
     }
 
     @VisibleForTesting
@@ -88,7 +92,7 @@
 
         PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler(
                 defaultHandler);
-        Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
+        Thread.setUncaughtExceptionPreHandler(uncaughtExceptionHandler);
         if (isDebuggable) {
             new Handler(mLooper).post(() -> {
                 // Plugin dependencies that don't have another good home can go here, but
@@ -290,6 +294,15 @@
         return false;
     }
 
+    public void handleWtfs() {
+        if (!mWtfsSet) {
+            mWtfsSet = true;
+            Log.setWtfHandler((tag, what, system) -> {
+                throw new CrashWhilePluginActiveException(what);
+            });
+        }
+    }
+
     @VisibleForTesting
     public static class PluginInstanceManagerFactory {
         public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
@@ -339,9 +352,12 @@
                 // disable all the plugins, so we can be sure that SysUI is running as
                 // best as possible.
                 for (PluginInstanceManager manager : mPluginMap.values()) {
-                    manager.disableAll();
+                    disabledAny |= manager.disableAll();
                 }
             }
+            if (disabledAny) {
+                throwable = new CrashWhilePluginActiveException(throwable);
+            }
 
             // Run the normal exception handler so we can crash and cleanup our state.
             mHandler.uncaughtException(thread, throwable);
@@ -358,4 +374,10 @@
             return disabledAny | checkStack(throwable.getCause());
         }
     }
+
+    private class CrashWhilePluginActiveException extends RuntimeException {
+        public CrashWhilePluginActiveException(Throwable throwable) {
+            super(throwable);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index c2a7ed3..aecf95f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -132,6 +132,11 @@
             // Preloads the next task
             RecentsConfiguration config = Recents.getConfiguration();
             if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
+                Rect windowRect = getWindowRect(null /* windowRectOverride */);
+                if (windowRect.isEmpty()) {
+                    return;
+                }
+
                 // Load the next task only if we aren't svelte
                 SystemServicesProxy ssp = Recents.getSystemServices();
                 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
@@ -146,8 +151,7 @@
                     // This callback is made when a new activity is launched and the old one is
                     // paused so ignore the current activity and try and preload the thumbnail for
                     // the previous one.
-                    updateDummyStackViewLayout(mBackgroundLayoutAlgorithm, stack,
-                            getWindowRect(null /* windowRectOverride */));
+                    updateDummyStackViewLayout(mBackgroundLayoutAlgorithm, stack, windowRect);
 
                     // Launched from app is always the worst case (in terms of how many
                     // thumbnails/tasks visible)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 46f9c04..afe5c91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -404,8 +404,8 @@
                 false /* collapseWhenFinished */);
         notifyBarPanelExpansionChanged();
         if (mVibrateOnOpening && !isHapticFeedbackDisabled(mContext)) {
-            AsyncTask.execute(
-                    () -> mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_TICK)));
+            AsyncTask.execute(() ->
+                    mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_TICK, false)));
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 03f42a6..d7f11f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -422,7 +422,7 @@
             mFloatingActionMode.finish();
         }
         cleanupFloatingActionModeViews();
-        mFloatingToolbar = new FloatingToolbar(mContext, mFakeWindow);
+        mFloatingToolbar = new FloatingToolbar(mFakeWindow);
         final FloatingActionMode mode =
                 new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar);
         mFloatingActionModeOriginatingView = originatingView;
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 5e71dd4..27c16d5 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -54,7 +54,8 @@
     SystemUI-proto \
     SystemUI-tags \
     legacy-android-test \
-    testables
+    testables \
+    truth-prebuilt \
 
 LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.car
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/AlwaysOnDisplayPolicyTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/AlwaysOnDisplayPolicyTest.java
new file mode 100644
index 0000000..abc2d0e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/AlwaysOnDisplayPolicyTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.doze;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.provider.Settings;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.format.DateUtils;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AlwaysOnDisplayPolicyTest extends SysuiTestCase {
+    private static final String ALWAYS_ON_DISPLAY_CONSTANTS_VALUE = "prox_screen_off_delay=1000"
+            + ",prox_cooldown_trigger=2000"
+            + ",prox_cooldown_period=3000"
+            + ",screen_brightness_array=1:2:3:4:5"
+            + ",dimming_scrim_array=5:4:3:2:1";
+
+    private String mPreviousConfig;
+
+    @Before
+    public void setUp() {
+        mPreviousConfig = Settings.Global.getString(mContext.getContentResolver(),
+                Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
+    }
+
+    @After
+    public void tearDown() {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS, mPreviousConfig);
+    }
+
+    @Test
+    public void testPolicy_valueNull_containsDefaultValue() throws Exception {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS, null);
+
+        AlwaysOnDisplayPolicy policy = new AlwaysOnDisplayPolicy(mContext);
+
+        assertThat(policy.proxScreenOffDelayMs).isEqualTo(10 * DateUtils.MINUTE_IN_MILLIS);
+        assertThat(policy.proxCooldownTriggerMs).isEqualTo(2 * DateUtils.MINUTE_IN_MILLIS);
+        assertThat(policy.proxCooldownPeriodMs).isEqualTo(5 * DateUtils.MINUTE_IN_MILLIS);
+        assertThat(policy.screenBrightnessArray).isEqualTo(mContext.getResources().getIntArray(
+                R.array.config_doze_brightness_sensor_to_brightness));
+        assertThat(policy.dimmingScrimArray).isEqualTo(mContext.getResources().getIntArray(
+                R.array.config_doze_brightness_sensor_to_scrim_opacity));
+    }
+
+    @Test
+    public void testPolicy_valueNotNull_containsValue() throws Exception {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS, ALWAYS_ON_DISPLAY_CONSTANTS_VALUE);
+
+        AlwaysOnDisplayPolicy policy = new AlwaysOnDisplayPolicy(mContext);
+
+        assertThat(policy.proxScreenOffDelayMs).isEqualTo(1000);
+        assertThat(policy.proxCooldownTriggerMs).isEqualTo(2000);
+        assertThat(policy.proxCooldownPeriodMs).isEqualTo(3000);
+        assertThat(policy.screenBrightnessArray).isEqualTo(new int[]{1, 2, 3, 4, 5});
+        assertThat(policy.dimmingScrimArray).isEqualTo(new int[]{5, 4, 3, 2, 1});
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index c275806..46e1d55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -60,7 +60,8 @@
         mSensorManager = new FakeSensorManager(mContext);
         mSensor = mSensorManager.getFakeLightSensor();
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
-                mSensor.getSensor(), mHostFake, null /* handler */);
+                mSensor.getSensor(), mHostFake, null /* handler */,
+                new AlwaysOnDisplayPolicy(mContext));
     }
 
     @Test
@@ -135,7 +136,8 @@
     @Test
     public void testNullSensor() throws Exception {
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
-                null /* sensor */, mHostFake, null /* handler */);
+                null /* sensor */, mHostFake, null /* handler */,
+                new AlwaysOnDisplayPolicy(mContext));
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
index b8e9fcd..94dbc2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
@@ -67,7 +67,7 @@
     public void setup() throws Exception {
         mDependency.injectTestDependency(Dependency.BG_LOOPER,
                 TestableLooper.get(this).getLooper());
-        mRealExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
+        mRealExceptionHandler = Thread.getUncaughtExceptionPreHandler();
         mMockExceptionHandler = mock(UncaughtExceptionHandler.class);
         mMockFactory = mock(PluginInstanceManagerFactory.class);
         mMockPluginInstance = mock(PluginInstanceManager.class);
@@ -167,9 +167,9 @@
     }
 
     private void resetExceptionHandler() {
-        mPluginExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
+        mPluginExceptionHandler = Thread.getUncaughtExceptionPreHandler();
         // Set back the real exception handler so the test can crash if it wants to.
-        Thread.setDefaultUncaughtExceptionHandler(mRealExceptionHandler);
+        Thread.setUncaughtExceptionPreHandler(mRealExceptionHandler);
     }
 
     @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index c4dc506..28bf856 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -312,6 +312,9 @@
 
   // Counts the number of AllSingleScanLister.onResult calls with a partial (channels) scan result
   optional int32 partial_all_single_scan_listener_results = 75;
+
+  // Pno scan metrics
+  optional PnoScanMetrics pno_scan_metrics = 76;
 }
 
 // Information that gets logged for every WiFi connection.
@@ -927,3 +930,23 @@
   // Number of scan results with num_connectable_networks
   optional int32 count = 2 [default = 0];
 }
+
+// Pno scan metrics
+// Here "Pno Scan" refers to the session of offloaded scans, these metrics count the result of a
+// single session, and not the individual scans within that session.
+message PnoScanMetrics {
+  // Total number of attempts to offload pno scans
+  optional int32 num_pno_scan_attempts = 1;
+
+  // Total number of pno scans failed
+  optional int32 num_pno_scan_failed = 2;
+
+  // Number of pno scans started successfully over offload
+  optional int32 num_pno_scan_started_over_offload = 3;
+
+  // Number of pno scans failed over offload
+  optional int32 num_pno_scan_failed_over_offload = 4;
+
+  // Total number of pno scans that found any network
+  optional int32 num_pno_found_network_events = 5;
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index a97e16b..83dfccb 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2397,6 +2397,7 @@
         public static final int MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS = 12;
         public static final int MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER = 13;
         public static final int MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER = 14;
+        public static final int MSG_INIT_SERVICE = 15;
 
         public MainHandler(Looper looper) {
             super(looper);
@@ -2495,6 +2496,11 @@
                 case MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER: {
                     showAccessibilityButtonTargetSelection();
                 } break;
+
+                case MSG_INIT_SERVICE: {
+                    final Service service = (Service) msg.obj;
+                    service.initializeService();
+                } break;
             }
         }
 
@@ -2950,20 +2956,31 @@
                 if (userState.mBindingServices.contains(mComponentName) || mWasConnectedAndDied) {
                     userState.mBindingServices.remove(mComponentName);
                     mWasConnectedAndDied = false;
-                    try {
-                       mServiceInterface.init(this, mId, mOverlayWindowToken);
-                       onUserStateChangedLocked(userState);
-                    } catch (RemoteException re) {
-                        Slog.w(LOG_TAG, "Error while setting connection for service: "
-                                + service, re);
-                        binderDied();
-                    }
+                    onUserStateChangedLocked(userState);
+                    // Initialize the service on the main handler after we're done setting up for
+                    // the new configuration (for example, initializing the input filter).
+                    mMainHandler.obtainMessage(MainHandler.MSG_INIT_SERVICE, this).sendToTarget();
                 } else {
                     binderDied();
                 }
             }
         }
 
+        private void initializeService() {
+            final IAccessibilityServiceClient serviceInterface;
+            synchronized (mLock) {
+                serviceInterface = mServiceInterface;
+            }
+            if (serviceInterface == null) return;
+            try {
+                serviceInterface.init(this, mId, mOverlayWindowToken);
+            } catch (RemoteException re) {
+                Slog.w(LOG_TAG, "Error while setting connection for service: "
+                        + serviceInterface, re);
+                binderDied();
+            }
+        }
+
         private boolean isCalledForCurrentUserLocked() {
             // We treat calls from a profile as if made by its parent as profiles
             // share the accessibility state of the parent. The call below
@@ -3313,8 +3330,8 @@
                     }
                     if (mMotionEventInjector != null) {
                         List<GestureDescription.GestureStep> steps = gestureSteps.getList();
-                         mMotionEventInjector.injectEvents(steps, mServiceInterface, sequence);
-                         return;
+                        mMotionEventInjector.injectEvents(steps, mServiceInterface, sequence);
+                        return;
                     } else {
                         Slog.e(LOG_TAG, "MotionEventInjector installation timed out");
                     }
@@ -3456,18 +3473,15 @@
                     return region;
                 }
                 MagnificationController magnificationController = getMagnificationController();
-                boolean forceRegistration = mSecurityPolicy.canControlMagnification(this);
-                boolean initiallyRegistered = magnificationController.isRegisteredLocked();
-                if (!initiallyRegistered && forceRegistration) {
-                    magnificationController.register();
-                }
+                boolean registeredJustForThisCall =
+                        registerMagnificationIfNeeded(magnificationController);
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     magnificationController.getMagnificationRegion(region);
                     return region;
                 } finally {
                     Binder.restoreCallingIdentity(identity);
-                    if (!initiallyRegistered && forceRegistration) {
+                    if (registeredJustForThisCall) {
                         magnificationController.unregister();
                     }
                 }
@@ -3481,11 +3495,17 @@
                     return 0.0f;
                 }
             }
+            MagnificationController magnificationController = getMagnificationController();
+            boolean registeredJustForThisCall =
+                    registerMagnificationIfNeeded(magnificationController);
             final long identity = Binder.clearCallingIdentity();
             try {
-                return getMagnificationController().getCenterX();
+                return magnificationController.getCenterX();
             } finally {
                 Binder.restoreCallingIdentity(identity);
+                if (registeredJustForThisCall) {
+                    magnificationController.unregister();
+                }
             }
         }
 
@@ -3496,14 +3516,30 @@
                     return 0.0f;
                 }
             }
+            MagnificationController magnificationController = getMagnificationController();
+            boolean registeredJustForThisCall =
+                    registerMagnificationIfNeeded(magnificationController);
             final long identity = Binder.clearCallingIdentity();
             try {
-                return getMagnificationController().getCenterY();
+                return magnificationController.getCenterY();
             } finally {
                 Binder.restoreCallingIdentity(identity);
+                if (registeredJustForThisCall) {
+                    magnificationController.unregister();
+                }
             }
         }
 
+        private boolean registerMagnificationIfNeeded(
+                MagnificationController magnificationController) {
+            if (!magnificationController.isRegisteredLocked()
+                    && mSecurityPolicy.canControlMagnification(this)) {
+                magnificationController.register();
+                return true;
+            }
+            return false;
+        }
+
         @Override
         public boolean resetMagnification(boolean animate) {
             synchronized (mLock) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 17292b4..adf536b 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2011,7 +2011,16 @@
                     break;
                 }
                 case NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED: {
-                    handleUpdateLinkProperties(nai, (LinkProperties) msg.obj);
+                    if (VDBG) {
+                        log("Update of LinkProperties for " + nai.name() +
+                                "; created=" + nai.created +
+                                "; everConnected=" + nai.everConnected);
+                    }
+                    LinkProperties oldLp = nai.linkProperties;
+                    synchronized (nai) {
+                        nai.linkProperties = (LinkProperties)msg.obj;
+                    }
+                    if (nai.everConnected) updateLinkProperties(nai, oldLp);
                     break;
                 }
                 case NetworkAgent.EVENT_NETWORK_INFO_CHANGED: {
@@ -2260,7 +2269,7 @@
             }
             nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
             mNetworkAgentInfos.remove(msg.replyTo);
-            nai.maybeStopClat();
+            maybeStopClat(nai);
             synchronized (mNetworkForNetId) {
                 // Remove the NetworkAgent, but don't mark the netId as
                 // available until we've told netd to delete it below.
@@ -4374,7 +4383,7 @@
         updateDnses(newLp, oldLp, netId);
 
         // Start or stop clat accordingly to network state.
-        networkAgent.updateClat(mNetd);
+        updateClat(networkAgent);
         if (isDefaultNetwork(networkAgent)) {
             handleApplyDefaultProxy(newLp.getHttpProxy());
         } else {
@@ -4389,6 +4398,32 @@
         mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent);
     }
 
+    private void updateClat(NetworkAgentInfo nai) {
+        if (Nat464Xlat.requiresClat(nai)) {
+            maybeStartClat(nai);
+        } else {
+            maybeStopClat(nai);
+        }
+    }
+
+    /** Ensure clat has started for this network. */
+    private void maybeStartClat(NetworkAgentInfo nai) {
+        if (nai.clatd != null && nai.clatd.isStarted()) {
+            return;
+        }
+        nai.clatd = new Nat464Xlat(mNetd, mTrackerHandler, nai);
+        nai.clatd.start();
+    }
+
+    /** Ensure clat has stopped for this network. */
+    private void maybeStopClat(NetworkAgentInfo nai) {
+        if (nai.clatd == null) {
+            return;
+        }
+        nai.clatd.stop();
+        nai.clatd = null;
+    }
+
     private void wakeupModifyInterface(String iface, NetworkCapabilities caps, boolean add) {
         // Marks are only available on WiFi interaces. Checking for
         // marks on unsupported interfaces is harmless.
@@ -4623,21 +4658,6 @@
         }
     }
 
-    public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) {
-        if (VDBG) {
-            log("Update of LinkProperties for " + nai.name() +
-                    "; created=" + nai.created +
-                    "; everConnected=" + nai.everConnected);
-        }
-        LinkProperties oldLp = nai.linkProperties;
-        synchronized (nai) {
-            nai.linkProperties = newLp;
-        }
-        if (nai.everConnected) {
-            updateLinkProperties(nai, oldLp);
-        }
-    }
-
     private void sendUpdatedScoreToFactories(NetworkAgentInfo nai) {
         for (int i = 0; i < nai.numNetworkRequests(); i++) {
             NetworkRequest nr = nai.requestAt(i);
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 4733840..046eb76 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -728,6 +728,9 @@
                     return timeout;
                 }
             }
+            if (!prebaked.shouldFallback()) {
+                return 0;
+            }
             final int id = prebaked.getId();
             if (id < 0 || id >= mFallbackEffects.length || mFallbackEffects[id] == null) {
                 Slog.w(TAG, "Failed to play prebaked effect, no fallback");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7356c93..810a6bf 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -466,7 +466,6 @@
     private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
     private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
     private static final String TAG_IMMERSIVE = TAG + POSTFIX_IMMERSIVE;
-    private static final String TAG_LOCKSCREEN = TAG + POSTFIX_LOCKSCREEN;
     private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
     private static final String TAG_LRU = TAG + POSTFIX_LRU;
     private static final String TAG_MU = TAG + POSTFIX_MU;
@@ -484,7 +483,6 @@
     private static final String TAG_UID_OBSERVERS = TAG + POSTFIX_UID_OBSERVERS;
     private static final String TAG_URI_PERMISSION = TAG + POSTFIX_URI_PERMISSION;
     private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY;
-    private static final String TAG_VISIBLE_BEHIND = TAG + POSTFIX_VISIBLE_BEHIND;
 
     // Mock "pretend we're idle now" broadcast action to the job scheduler; declared
     // here so that while the job scheduler can depend on AMS, the other way around
@@ -1473,7 +1471,7 @@
      * Flag that indicates if multi-window is enabled.
      *
      * For any particular form of multi-window to be enabled, generic multi-window must be enabled
-     * in {@link com.android.internal.R.bool.config_supportsMultiWindow} config or
+     * in {@link com.android.internal.R.bool#config_supportsMultiWindow} config or
      * {@link Settings.Global#DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES} development option set.
      * At least one of the forms of multi-window must be enabled in order for this flag to be
      * initialized to 'true'.
@@ -1562,6 +1560,13 @@
     final ArrayList<UidRecord.ChangeItem> mPendingUidChanges = new ArrayList<>();
     final ArrayList<UidRecord.ChangeItem> mAvailUidChanges = new ArrayList<>();
 
+    OomAdjObserver mCurOomAdjObserver;
+    int mCurOomAdjUid;
+
+    interface OomAdjObserver {
+        void onOomAdjMessage(String msg);
+    }
+
     /**
      * Runtime CPU use collection thread.  This object's lock is used to
      * perform synchronization with the thread (notifying it to run).
@@ -1683,6 +1688,7 @@
     static final int DISPATCH_PENDING_INTENT_CANCEL_MSG = 67;
     static final int PUSH_TEMP_WHITELIST_UI_MSG = 68;
     static final int SERVICE_FOREGROUND_CRASH_MSG = 69;
+    static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
     static final int START_USER_SWITCH_FG_MSG = 712;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
@@ -1918,6 +1924,9 @@
             case DISPATCH_UIDS_CHANGED_UI_MSG: {
                 dispatchUidsChanged();
             } break;
+            case DISPATCH_OOM_ADJ_OBSERVER_MSG: {
+                dispatchOomAdjObserver((String)msg.obj);
+            } break;
             case PUSH_TEMP_WHITELIST_UI_MSG: {
                 pushTempWhitelist();
             } break;
@@ -4456,6 +4465,38 @@
         }
     }
 
+    void dispatchOomAdjObserver(String msg) {
+        OomAdjObserver observer;
+        synchronized (this) {
+            observer = mCurOomAdjObserver;
+        }
+
+        if (observer != null) {
+            observer.onOomAdjMessage(msg);
+        }
+    }
+
+    void setOomAdjObserver(int uid, OomAdjObserver observer) {
+        synchronized (this) {
+            mCurOomAdjUid = uid;
+            mCurOomAdjObserver = observer;
+        }
+    }
+
+    void clearOomAdjObserver() {
+        synchronized (this) {
+            mCurOomAdjUid = -1;
+            mCurOomAdjObserver = null;
+        }
+    }
+
+    void reportOomAdjMessageLocked(String tag, String msg) {
+        Slog.d(tag, msg);
+        if (mCurOomAdjObserver != null) {
+            mUiHandler.obtainMessage(DISPATCH_OOM_ADJ_OBSERVER_MSG, msg).sendToTarget();
+        }
+    }
+
     @Override
     public final int startActivity(IApplicationThread caller, String callingPackage,
             Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
@@ -13423,7 +13464,7 @@
     /**
      * Schedule the given thread a normal scheduling priority.
      *
-     * @param newTid the tid of the thread to adjust the scheduling of.
+     * @param tid the tid of the thread to adjust the scheduling of.
      * @param suppressLogs {@code true} if any error logging should be disabled.
      *
      * @return {@code true} if this succeeded.
@@ -13436,6 +13477,10 @@
             if (!suppressLogs) {
                 Slog.w(TAG, "Failed to set scheduling policy, thread does not exist:\n" + e);
             }
+        } catch (SecurityException e) {
+            if (!suppressLogs) {
+                Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
+            }
         }
         return false;
     }
@@ -13443,7 +13488,7 @@
     /**
      * Schedule the given thread an FIFO scheduling priority.
      *
-     * @param newTid the tid of the thread to adjust the scheduling of.
+     * @param tid the tid of the thread to adjust the scheduling of.
      * @param suppressLogs {@code true} if any error logging should be disabled.
      *
      * @return {@code true} if this succeeded.
@@ -13456,6 +13501,10 @@
             if (!suppressLogs) {
                 Slog.w(TAG, "Failed to set scheduling policy, thread does not exist:\n" + e);
             }
+        } catch (SecurityException e) {
+            if (!suppressLogs) {
+                Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
+            }
         }
         return false;
     }
@@ -21938,9 +21987,11 @@
 
         if (app.curAdj != app.setAdj) {
             ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);
-            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
-                    "Set " + app.pid + " " + app.processName + " adj " + app.curAdj + ": "
-                    + app.adjType);
+            if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.info.uid) {
+                String msg = "Set " + app.pid + " " + app.processName + " adj "
+                        + app.curAdj + ": " + app.adjType;
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
+            }
             app.setAdj = app.curAdj;
             app.verifiedAdj = ProcessList.INVALID_ADJ;
         }
@@ -21948,9 +21999,11 @@
         if (app.setSchedGroup != app.curSchedGroup) {
             int oldSchedGroup = app.setSchedGroup;
             app.setSchedGroup = app.curSchedGroup;
-            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
-                    "Setting sched group of " + app.processName
-                    + " to " + app.curSchedGroup);
+            if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.uid) {
+                String msg = "Setting sched group of " + app.processName
+                        + " to " + app.curSchedGroup;
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
+            }
             if (app.waitingToKill != null && app.curReceivers.isEmpty()
                     && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
                 app.kill(app.waitingToKill, true);
@@ -22009,13 +22062,21 @@
                                app.curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
                         mVrController.onTopProcChangedLocked(app);
                         if (mUseFifoUiScheduling) {
-                            // Reset UI pipeline to SCHED_OTHER
-                            setThreadScheduler(app.pid, SCHED_OTHER, 0);
-                            setThreadPriority(app.pid, app.savedPriority);
-                            if (app.renderThreadTid != 0) {
-                                setThreadScheduler(app.renderThreadTid,
-                                    SCHED_OTHER, 0);
-                                setThreadPriority(app.renderThreadTid, -4);
+                            try {
+                                // Reset UI pipeline to SCHED_OTHER
+                                setThreadScheduler(app.pid, SCHED_OTHER, 0);
+                                setThreadPriority(app.pid, app.savedPriority);
+                                if (app.renderThreadTid != 0) {
+                                    setThreadScheduler(app.renderThreadTid,
+                                        SCHED_OTHER, 0);
+                                    setThreadPriority(app.renderThreadTid, -4);
+                                }
+                            } catch (IllegalArgumentException e) {
+                                Slog.w(TAG,
+                                        "Failed to set scheduling policy, thread does not exist:\n"
+                                                + e);
+                            } catch (SecurityException e) {
+                                Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
                             }
                         } else {
                             // Reset priority for top app UI and render threads
@@ -22087,9 +22148,11 @@
                     "Not requesting PSS of " + app + ": next=" + (app.nextPssTime-now));
         }
         if (app.setProcState != app.curProcState) {
-            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
-                    "Proc state change of " + app.processName
-                            + " to " + app.curProcState);
+            if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.uid) {
+                String msg = "Proc state change of " + app.processName
+                        + " to " + app.curProcState;
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
+            }
             boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE;
             boolean curImportant = app.curProcState < ActivityManager.PROCESS_STATE_SERVICE;
             if (setImportant && !curImportant) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 45357cb..8488e52 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -55,7 +55,6 @@
 import android.util.ArrayMap;
 import android.util.DebugUtils;
 import android.util.DisplayMetrics;
-import android.view.IWindowManager;
 
 import com.android.internal.util.HexDump;
 import com.android.internal.util.Preconditions;
@@ -1296,19 +1295,24 @@
         return 0;
     }
 
-    static final class MyUidObserver extends IUidObserver.Stub {
+    static final class MyUidObserver extends IUidObserver.Stub
+            implements ActivityManagerService.OomAdjObserver {
         final IActivityManager mInterface;
+        final ActivityManagerService mInternal;
         final PrintWriter mPw;
         final InputStream mInput;
+        final int mUid;
 
         static final int STATE_NORMAL = 0;
 
         int mState;
 
-        MyUidObserver(IActivityManager iam, PrintWriter pw, InputStream input) {
-            mInterface = iam;
+        MyUidObserver(ActivityManagerService service, PrintWriter pw, InputStream input, int uid) {
+            mInterface = service;
+            mInternal = service;
             mPw = pw;
             mInput = input;
+            mUid = uid;
         }
 
         @Override
@@ -1367,6 +1371,15 @@
             }
         }
 
+        @Override
+        public void onOomAdjMessage(String msg) {
+            synchronized (this) {
+                mPw.print("# ");
+                mPw.println(msg);
+                mPw.flush();
+            }
+        }
+
         void printMessageForState() {
             switch (mState) {
                 case STATE_NORMAL:
@@ -1385,6 +1398,9 @@
                         | ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_PROCSTATE
                         | ActivityManager.UID_OBSERVER_IDLE | ActivityManager.UID_OBSERVER_CACHED,
                         ActivityManager.PROCESS_STATE_UNKNOWN, null);
+                if (mUid >= 0) {
+                    mInternal.setOomAdjObserver(mUid, this);
+                }
                 mState = STATE_NORMAL;
 
                 InputStreamReader converter = new InputStreamReader(mInput);
@@ -1414,6 +1430,9 @@
                 e.printStackTrace(mPw);
                 mPw.flush();
             } finally {
+                if (mUid >= 0) {
+                    mInternal.clearOomAdjObserver();
+                }
                 mInterface.unregisterUidObserver(this);
             }
         }
@@ -1421,12 +1440,18 @@
 
     int runWatchUids(PrintWriter pw) throws RemoteException {
         String opt;
+        int uid = -1;
         while ((opt=getNextOption()) != null) {
-            getErrPrintWriter().println("Error: Unknown option: " + opt);
-            return -1;
+            if (opt.equals("--oom")) {
+                uid = Integer.parseInt(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: Unknown option: " + opt);
+                return -1;
+
+            }
         }
 
-        MyUidObserver controller = new MyUidObserver(mInterface, pw, getRawInputStream());
+        MyUidObserver controller = new MyUidObserver(mInternal, pw, getRawInputStream(), uid);
         controller.run();
         return 0;
     }
@@ -1858,8 +1883,12 @@
                 level = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
                 break;
             default:
-                getErrPrintWriter().println("Error: Unknown level option: " + levelArg);
-                return -1;
+                try {
+                    level = Integer.parseInt(levelArg);
+                } catch (NumberFormatException e) {
+                    getErrPrintWriter().println("Error: Unknown level option: " + levelArg);
+                    return -1;
+                }
         }
         if (!mInterface.setProcessMemoryTrimLevel(proc, userId, level)) {
             getErrPrintWriter().println("Unknown error: failed to set trim level");
@@ -2744,8 +2773,9 @@
             pw.println("  monitor [--gdb <port>]");
             pw.println("      Start monitoring for crashes or ANRs.");
             pw.println("      --gdb: start gdbserv on the given port at crash/ANR");
-            pw.println("  watch-uids [--gdb <port>]");
+            pw.println("  watch-uids [--oom <uid>");
             pw.println("      Start watching for and reporting uid state changes.");
+            pw.println("      --oom: specify a uid for which to report detailed change messages.");
             pw.println("  hang [--allow-restart]");
             pw.println("      Hang the system.");
             pw.println("      --allow-restart: allow watchdog to perform normal system restart");
@@ -2804,7 +2834,7 @@
             pw.println("      Returns the inactive state of an app.");
             pw.println("  send-trim-memory [--user <USER_ID>] <PROCESS>");
             pw.println("          [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]");
-            pw.println("      Send a memory trim event to a <PROCESS>.");
+            pw.println("      Send a memory trim event to a <PROCESS>.  May also supply a raw trim int level.");
             pw.println("  display [COMMAND] [...]: sub-commands for operating on displays.");
             pw.println("       move-stack <STACK_ID> <DISPLAY_ID>");
             pw.println("           Move <STACK_ID> from its current display to <DISPLAY_ID>.");
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 871ccb9..5b27c9c 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -659,14 +659,14 @@
         }
     }
 
-    void updatePictureInPictureMode(Rect targetStackBounds) {
+    void updatePictureInPictureMode(Rect targetStackBounds, boolean forceUpdate) {
         if (task == null || task.getStack() == null || app == null || app.thread == null) {
             return;
         }
 
         final boolean inPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID) &&
                 (targetStackBounds != null);
-        if (inPictureInPictureMode != mLastReportedPictureInPictureMode) {
+        if (inPictureInPictureMode != mLastReportedPictureInPictureMode || forceUpdate) {
             // Picture-in-picture mode changes also trigger a multi-window mode change as well, so
             // update that here in order
             mLastReportedPictureInPictureMode = inPictureInPictureMode;
@@ -681,8 +681,7 @@
     private void schedulePictureInPictureModeChanged(Configuration overrideConfig) {
         try {
             app.thread.schedulePictureInPictureModeChanged(appToken,
-                    mLastReportedPictureInPictureMode,
-                    overrideConfig);
+                    mLastReportedPictureInPictureMode, overrideConfig);
         } catch (Exception e) {
             // If process died, no one cares.
         }
@@ -1592,7 +1591,12 @@
     }
 
     void notifyUnknownVisibilityLaunched() {
-        mWindowContainerController.notifyUnknownVisibilityLaunched();
+
+        // No display activities never add a window, so there is no point in waiting them for
+        // relayout.
+        if (!noDisplay) {
+            mWindowContainerController.notifyUnknownVisibilityLaunched();
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d16ae185..7bbc6cc 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1336,6 +1336,10 @@
 
             r.app = app;
 
+            if (mKeyguardController.isKeyguardLocked()) {
+                r.notifyUnknownVisibilityLaunched();
+            }
+
             // Have the window manager re-evaluate the orientation of the screen based on the new
             // activity order.  Note that as a result of this, it can call back into the activity
             // manager with a new orientation.  We don't care about that, because the activity is
@@ -1362,9 +1366,6 @@
                 r.setVisibility(true);
             }
 
-            if (mKeyguardController.isKeyguardLocked()) {
-                r.notifyUnknownVisibilityLaunched();
-            }
             final int applicationInfoUid =
                     (r.info.applicationInfo != null) ? r.info.applicationInfo.uid : -1;
             if ((r.userId != app.userId) || (r.appInfo.uid != applicationInfoUid)) {
@@ -4410,31 +4411,29 @@
             return;
         }
 
-        scheduleUpdatePictureInPictureModeIfNeeded(task, stack.mBounds, false /* immediate */);
+        scheduleUpdatePictureInPictureModeIfNeeded(task, stack.mBounds);
     }
 
-    void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds,
-            boolean immediate) {
-
-        if (immediate) {
-            mHandler.removeMessages(REPORT_PIP_MODE_CHANGED_MSG);
-            for (int i = task.mActivities.size() - 1; i >= 0; i--) {
-                final ActivityRecord r = task.mActivities.get(i);
-                if (r.app != null && r.app.thread != null) {
-                    r.updatePictureInPictureMode(targetStackBounds);
-                }
+    void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds) {
+        for (int i = task.mActivities.size() - 1; i >= 0; i--) {
+            final ActivityRecord r = task.mActivities.get(i);
+            if (r.app != null && r.app.thread != null) {
+                mPipModeChangedActivities.add(r);
             }
-        } else {
-            for (int i = task.mActivities.size() - 1; i >= 0; i--) {
-                final ActivityRecord r = task.mActivities.get(i);
-                if (r.app != null && r.app.thread != null) {
-                    mPipModeChangedActivities.add(r);
-                }
-            }
-            mPipModeChangedTargetStackBounds = targetStackBounds;
+        }
+        mPipModeChangedTargetStackBounds = targetStackBounds;
 
-            if (!mHandler.hasMessages(REPORT_PIP_MODE_CHANGED_MSG)) {
-                mHandler.sendEmptyMessage(REPORT_PIP_MODE_CHANGED_MSG);
+        if (!mHandler.hasMessages(REPORT_PIP_MODE_CHANGED_MSG)) {
+            mHandler.sendEmptyMessage(REPORT_PIP_MODE_CHANGED_MSG);
+        }
+    }
+
+    void updatePictureInPictureMode(TaskRecord task, Rect targetStackBounds, boolean forceUpdate) {
+        mHandler.removeMessages(REPORT_PIP_MODE_CHANGED_MSG);
+        for (int i = task.mActivities.size() - 1; i >= 0; i--) {
+            final ActivityRecord r = task.mActivities.get(i);
+            if (r.app != null && r.app.thread != null) {
+                r.updatePictureInPictureMode(targetStackBounds, forceUpdate);
             }
         }
     }
@@ -4496,7 +4495,8 @@
                     synchronized (mService) {
                         for (int i = mPipModeChangedActivities.size() - 1; i >= 0; i--) {
                             final ActivityRecord r = mPipModeChangedActivities.remove(i);
-                            r.updatePictureInPictureMode(mPipModeChangedTargetStackBounds);
+                            r.updatePictureInPictureMode(mPipModeChangedTargetStackBounds,
+                                    false /* forceUpdate */);
                         }
                     }
                 } break;
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 7c7eda7..d835454 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1445,20 +1445,19 @@
         r.receiverTime = now;
         r.anrCount++;
 
-        // Current receiver has passed its expiration date.
-        if (r.nextReceiver <= 0) {
-            Slog.w(TAG, "Timeout on receiver with nextReceiver <= 0");
-            return;
-        }
-
         ProcessRecord app = null;
         String anrMessage = null;
 
-        Object curReceiver = r.receivers.get(r.nextReceiver-1);
-        r.delivery[r.nextReceiver-1] = BroadcastRecord.DELIVERY_TIMEOUT;
-        Slog.w(TAG, "Receiver during timeout: " + curReceiver);
+        Object curReceiver;
+        if (r.nextReceiver > 0) {
+            curReceiver = r.receivers.get(r.nextReceiver-1);
+            r.delivery[r.nextReceiver-1] = BroadcastRecord.DELIVERY_TIMEOUT;
+        } else {
+            curReceiver = r.curReceiver;
+        }
+        Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
         logBroadcastReceiverDiscardLocked(r);
-        if (curReceiver instanceof BroadcastFilter) {
+        if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
             BroadcastFilter bf = (BroadcastFilter)curReceiver;
             if (bf.receiverList.pid != 0
                     && bf.receiverList.pid != ActivityManagerService.MY_PID) {
diff --git a/services/core/java/com/android/server/am/PinnedActivityStack.java b/services/core/java/com/android/server/am/PinnedActivityStack.java
index 392fbb2..a601ee1 100644
--- a/services/core/java/com/android/server/am/PinnedActivityStack.java
+++ b/services/core/java/com/android/server/am/PinnedActivityStack.java
@@ -91,15 +91,16 @@
         return mWindowContainerController.deferScheduleMultiWindowModeChanged();
     }
 
-    public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {
+    public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
+            boolean forceUpdate) {
         // It is guaranteed that the activities requiring the update will be in the pinned stack at
         // this point (either reparented before the animation into PiP, or before reparenting after
         // the animation out of PiP)
         synchronized(this) {
             ArrayList<TaskRecord> tasks = getAllTasks();
             for (int i = 0; i < tasks.size(); i++ ) {
-                mStackSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(tasks.get(i),
-                        targetStackBounds, true /* immediate */);
+                mStackSupervisor.updatePictureInPictureMode(tasks.get(i), targetStackBounds,
+                        forceUpdate);
             }
         }
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index faf4729..91b1591 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -801,12 +801,23 @@
     public void systemReady() {
         sendMsg(mAudioHandler, MSG_SYSTEM_READY, SENDMSG_QUEUE,
                 0, 0, null, 0);
-        try {
-            ActivityManager.getService().registerUidObserver(mUidObserver,
-                    ActivityManager.UID_OBSERVER_CACHED | ActivityManager.UID_OBSERVER_GONE,
-                    ActivityManager.PROCESS_STATE_UNKNOWN, null);
-        } catch (RemoteException e) {
-            // ignored; both services live in system_server
+        if (false) {
+            // This is turned off for now, because it is racy and thus causes apps to break.
+            // Currently banning a uid means that if an app tries to start playing an audio
+            // stream, that will be preventing, and unbanning it will not allow that stream
+            // to resume.  However these changes in uid state are racy with what the app is doing,
+            // so that after taking a process out of the cached state we can't guarantee that
+            // we will unban the uid before the app actually tries to start playing audio.
+            // (To do that, the activity manager would need to wait until it knows for sure
+            // that the ban has been removed, before telling the app to do whatever it is
+            // supposed to do that caused it to go out of the cached state.)
+            try {
+                ActivityManager.getService().registerUidObserver(mUidObserver,
+                        ActivityManager.UID_OBSERVER_CACHED | ActivityManager.UID_OBSERVER_GONE,
+                        ActivityManager.PROCESS_STATE_UNKNOWN, null);
+            } catch (RemoteException e) {
+                // ignored; both services live in system_server
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index 10c8b8b..f8d23d4 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -20,21 +20,22 @@
 import android.net.ConnectivityManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.NetworkAgent;
 import android.net.RouteInfo;
+import android.os.Handler;
+import android.os.Message;
 import android.os.INetworkManagementService;
 import android.os.RemoteException;
 import android.util.Slog;
 
-import com.android.internal.util.ArrayUtils;
 import com.android.server.net.BaseNetworkObserver;
+import com.android.internal.util.ArrayUtils;
 
 import java.net.Inet4Address;
 import java.util.Objects;
 
 /**
- * Class to manage a 464xlat CLAT daemon. Nat464Xlat is not thread safe and should be manipulated
- * from a consistent and unique thread context. It is the responsability of ConnectivityService to
- * call into this class from its own Handler thread.
+ * Class to manage a 464xlat CLAT daemon.
  *
  * @hide
  */
@@ -54,6 +55,9 @@
 
     private final INetworkManagementService mNMService;
 
+    // ConnectivityService Handler for LinkProperties updates.
+    private final Handler mHandler;
+
     // The network we're running on, and its type.
     private final NetworkAgentInfo mNetwork;
 
@@ -63,12 +67,16 @@
         RUNNING;    // start() called, and the stacked iface is known to be up.
     }
 
+    // Once mIface is non-null and isStarted() is true, methods called by ConnectivityService on
+    // its handler thread must not modify any internal state variables; they are only updated by the
+    // interface observers, called on the notification threads.
     private String mBaseIface;
     private String mIface;
-    private State mState = State.IDLE;
+    private volatile State mState = State.IDLE;
 
-    public Nat464Xlat(INetworkManagementService nmService, NetworkAgentInfo nai) {
+    public Nat464Xlat(INetworkManagementService nmService, Handler handler, NetworkAgentInfo nai) {
         mNMService = nmService;
+        mHandler = handler;
         mNetwork = nai;
     }
 
@@ -97,13 +105,6 @@
     }
 
     /**
-     * @return true if clatd has been started but the stacked interface is not yet up.
-     */
-    public boolean isStarting() {
-        return mState == State.STARTING;
-    }
-
-    /**
      * @return true if clatd has been started and the stacked interface is up.
      */
     public boolean isRunning() {
@@ -120,7 +121,7 @@
     }
 
     /**
-     * Clears internal state.
+     * Clears internal state. Must not be called by ConnectivityService.
      */
     private void enterIdleState() {
         mIface = null;
@@ -129,7 +130,7 @@
     }
 
     /**
-     * Starts the clat daemon.
+     * Starts the clat daemon. Called by ConnectivityService on the handler thread.
      */
     public void start() {
         if (isStarted()) {
@@ -166,7 +167,7 @@
     }
 
     /**
-     * Stops the clat daemon.
+     * Stops the clat daemon. Called by ConnectivityService on the handler thread.
      */
     public void stop() {
         if (!isStarted()) {
@@ -180,8 +181,15 @@
         } catch(RemoteException|IllegalStateException e) {
             Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e);
         }
-        // When clatd stops and its interface is deleted, handleInterfaceRemoved() will trigger
-        // ConnectivityService#handleUpdateLinkProperties and call enterIdleState().
+        // When clatd stops and its interface is deleted, interfaceRemoved() will notify
+        // ConnectivityService and call enterIdleState().
+    }
+
+    private void updateConnectivityService(LinkProperties lp) {
+        Message msg = mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED, lp);
+        msg.replyTo = mNetwork.messenger;
+        Slog.i(TAG, "sending message to ConnectivityService: " + msg);
+        msg.sendToTarget();
     }
 
     /**
@@ -249,15 +257,19 @@
     }
 
     /**
-     * Adds stacked link on base link and transitions to RUNNING state.
+     * Adds stacked link on base link and transitions to Running state
+     * This is called by the InterfaceObserver on its own thread, so can race with stop().
      */
-    private void handleInterfaceLinkStateChanged(String iface, boolean up) {
-        if (!isStarting() || !up || !Objects.equals(mIface, iface)) {
+    @Override
+    public void interfaceLinkStateChanged(String iface, boolean up) {
+        if (!isStarted() || !up || !Objects.equals(mIface, iface)) {
+            return;
+        }
+        if (isRunning()) {
             return;
         }
         LinkAddress clatAddress = getLinkAddress(iface);
         if (clatAddress == null) {
-            Slog.e(TAG, "cladAddress was null for stacked iface " + iface);
             return;
         }
         mState = State.RUNNING;
@@ -267,14 +279,15 @@
         maybeSetIpv6NdOffload(mBaseIface, false);
         LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
         lp.addStackedLink(makeLinkProperties(clatAddress));
-        mNetwork.connService.handleUpdateLinkProperties(mNetwork, lp);
+        updateConnectivityService(lp);
     }
 
-    /**
-     * Removes stacked link on base link and transitions to IDLE state.
-     */
-    private void handleInterfaceRemoved(String iface) {
-        if (!isRunning() || !Objects.equals(mIface, iface)) {
+    @Override
+    public void interfaceRemoved(String iface) {
+        if (!isStarted() || !Objects.equals(mIface, iface)) {
+            return;
+        }
+        if (!isRunning()) {
             return;
         }
 
@@ -282,28 +295,21 @@
         // The interface going away likely means clatd has crashed. Ask netd to stop it,
         // because otherwise when we try to start it again on the same base interface netd
         // will complain that it's already started.
+        //
+        // Note that this method can be called by the interface observer at the same time
+        // that ConnectivityService calls stop(). In this case, the second call to
+        // stopClatd() will just throw IllegalStateException, which we'll ignore.
         try {
             mNMService.unregisterObserver(this);
-            // TODO: add STOPPING state to avoid calling stopClatd twice.
             mNMService.stopClatd(mBaseIface);
-        } catch(RemoteException|IllegalStateException e) {
-            Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e);
+        } catch (RemoteException|IllegalStateException e) {
+            // Well, we tried.
         }
         maybeSetIpv6NdOffload(mBaseIface, true);
         LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
         lp.removeStackedLink(mIface);
         enterIdleState();
-        mNetwork.connService.handleUpdateLinkProperties(mNetwork, lp);
-    }
-
-    @Override
-    public void interfaceLinkStateChanged(String iface, boolean up) {
-        mNetwork.handler.post(() -> { handleInterfaceLinkStateChanged(iface, up); });
-    }
-
-    @Override
-    public void interfaceRemoved(String iface) {
-        mNetwork.handler.post(() -> { handleInterfaceRemoved(iface); });
+        updateConnectivityService(lp);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 7c4ef0f..872923a 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -27,9 +27,7 @@
 import android.net.NetworkRequest;
 import android.net.NetworkState;
 import android.os.Handler;
-import android.os.INetworkManagementService;
 import android.os.Messenger;
-import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
@@ -249,9 +247,9 @@
 
     private static final String TAG = ConnectivityService.class.getSimpleName();
     private static final boolean VDBG = false;
-    public final ConnectivityService connService;
+    private final ConnectivityService mConnService;
     private final Context mContext;
-    final Handler handler;
+    private final Handler mHandler;
 
     public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
             LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
@@ -263,10 +261,10 @@
         linkProperties = lp;
         networkCapabilities = nc;
         currentScore = score;
-        this.connService = connService;
+        mConnService = connService;
         mContext = context;
-        this.handler = handler;
-        networkMonitor = connService.createNetworkMonitor(context, handler, this, defaultRequest);
+        mHandler = handler;
+        networkMonitor = mConnService.createNetworkMonitor(context, handler, this, defaultRequest);
         networkMisc = misc;
     }
 
@@ -432,7 +430,7 @@
     private boolean ignoreWifiUnvalidationPenalty() {
         boolean isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
                 networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-        boolean avoidBadWifi = connService.avoidBadWifi() || avoidUnvalidated;
+        boolean avoidBadWifi = mConnService.avoidBadWifi() || avoidUnvalidated;
         return isWifi && !avoidBadWifi && everValidated;
     }
 
@@ -516,8 +514,8 @@
         }
 
         if (newExpiry > 0) {
-            mLingerMessage = connService.makeWakeupMessage(
-                    mContext, handler,
+            mLingerMessage = mConnService.makeWakeupMessage(
+                    mContext, mHandler,
                     "NETWORK_LINGER_COMPLETE." + network.netId,
                     EVENT_NETWORK_LINGER_COMPLETE, this);
             mLingerMessage.schedule(newExpiry);
@@ -553,32 +551,6 @@
         for (LingerTimer timer : mLingerTimers) { pw.println(timer); }
     }
 
-    public void updateClat(INetworkManagementService netd) {
-        if (Nat464Xlat.requiresClat(this)) {
-            maybeStartClat(netd);
-        } else {
-            maybeStopClat();
-        }
-    }
-
-    /** Ensure clat has started for this network. */
-    public void maybeStartClat(INetworkManagementService netd) {
-        if (clatd != null && clatd.isStarted()) {
-            return;
-        }
-        clatd = new Nat464Xlat(netd, this);
-        clatd.start();
-    }
-
-    /** Ensure clat has stopped for this network. */
-    public void maybeStopClat() {
-        if (clatd == null) {
-            return;
-        }
-        clatd.stop();
-        clatd = null;
-    }
-
     public String toString() {
         return "NetworkAgentInfo{ ni{" + networkInfo + "}  " +
                 "network{" + network + "}  nethandle{" + network.getNetworkHandle() + "}  " +
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 3fc76c0..4dab32c 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -501,11 +501,12 @@
             if (DEBUG) {
                 Slog.d(TAG, "Receieved: " + action);
             }
+            final String pkgName = getPackageName(intent);
+            final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+
             if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
                 // Purge the app's jobs if the whole package was just disabled.  When this is
                 // the case the component name will be a bare package name.
-                final String pkgName = getPackageName(intent);
-                final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
                 if (pkgName != null && pkgUid != -1) {
                     final String[] changedComponents = intent.getStringArrayExtra(
                             Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
@@ -525,7 +526,8 @@
                                             Slog.d(TAG, "Removing jobs for package " + pkgName
                                                     + " in user " + userId);
                                         }
-                                        cancelJobsForUid(pkgUid, "app package state changed");
+                                        cancelJobsForPackageAndUid(pkgName, pkgUid,
+                                                "app disabled");
                                     }
                                 } catch (RemoteException|IllegalArgumentException e) {
                                     /*
@@ -554,7 +556,7 @@
                     if (DEBUG) {
                         Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
                     }
-                    cancelJobsForUid(uidRemoved, "app uninstalled");
+                    cancelJobsForPackageAndUid(pkgName, uidRemoved, "app uninstalled");
                 }
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
@@ -565,8 +567,6 @@
             } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
                 // Has this package scheduled any jobs, such that we will take action
                 // if it were to be force-stopped?
-                final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
-                final String pkgName = intent.getData().getSchemeSpecificPart();
                 if (pkgUid != -1) {
                     List<JobStatus> jobsForUid;
                     synchronized (mLock) {
@@ -585,13 +585,11 @@
                 }
             } else if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
                 // possible force-stop
-                final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
-                final String pkgName = intent.getData().getSchemeSpecificPart();
                 if (pkgUid != -1) {
                     if (DEBUG) {
                         Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
                     }
-                    cancelJobsForPackageAndUid(pkgName, pkgUid);
+                    cancelJobsForPackageAndUid(pkgName, pkgUid, "app force stopped");
                 }
             }
         }
@@ -762,13 +760,17 @@
         }
     }
 
-    void cancelJobsForPackageAndUid(String pkgName, int uid) {
+    void cancelJobsForPackageAndUid(String pkgName, int uid, String reason) {
+        if ("android".equals(pkgName)) {
+            Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
+            return;
+        }
         synchronized (mLock) {
             final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
             for (int i = jobsForUid.size() - 1; i >= 0; i--) {
                 final JobStatus job = jobsForUid.get(i);
                 if (job.getSourcePackageName().equals(pkgName)) {
-                    cancelJobImplLocked(job, null, "app force stopped");
+                    cancelJobImplLocked(job, null, reason);
                 }
             }
         }
@@ -783,8 +785,7 @@
      */
     public void cancelJobsForUid(int uid, String reason) {
         if (uid == Process.SYSTEM_UID) {
-            // This really shouldn't happen.
-            Slog.wtfStack(TAG, "cancelJobsForUid() called for system uid");
+            Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
             return;
         }
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 031bdd0..d3fd3a9 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -261,6 +261,13 @@
         return mRunningJob;
     }
 
+    /**
+     * Used only for debugging. Will return <code>"&lt;null&gt;"</code> if there is no job running.
+     */
+    private String getRunningJobNameLocked() {
+        return mRunningJob != null ? mRunningJob.toShortString() : "<null>";
+    }
+
     /** Called externally when a job that was scheduled for execution should be cancelled. */
     void cancelExecutingJobLocked(int reason, String debugReason) {
         doCancelLocked(reason, debugReason);
@@ -522,7 +529,7 @@
     /** Start the job on the service. */
     private void handleServiceBoundLocked() {
         if (DEBUG) {
-            Slog.d(TAG, "handleServiceBound for " + mRunningJob.toShortString());
+            Slog.d(TAG, "handleServiceBound for " + getRunningJobNameLocked());
         }
         if (mVerb != VERB_BINDING) {
             Slog.e(TAG, "Sending onStartJob for a job that isn't pending. "
@@ -639,36 +646,34 @@
     private void handleOpTimeoutLocked() {
         switch (mVerb) {
             case VERB_BINDING:
-                Slog.w(TAG, "Time-out while trying to bind " + mRunningJob.toShortString() +
-                        ", dropping.");
+                Slog.w(TAG, "Time-out while trying to bind " + getRunningJobNameLocked()
+                        + ", dropping.");
                 closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while binding");
                 break;
             case VERB_STARTING:
                 // Client unresponsive - wedged or failed to respond in time. We don't really
                 // know what happened so let's log it and notify the JobScheduler
                 // FINISHED/NO-RETRY.
-                Slog.w(TAG, "No response from client for onStartJob " +
-                        mRunningJob != null ? mRunningJob.toShortString() : "<null>");
+                Slog.w(TAG, "No response from client for onStartJob "
+                        + getRunningJobNameLocked());
                 closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while starting");
                 break;
             case VERB_STOPPING:
                 // At least we got somewhere, so fail but ask the JobScheduler to reschedule.
-                Slog.w(TAG, "No response from client for onStopJob " +
-                        mRunningJob != null ? mRunningJob.toShortString() : "<null>");
+                Slog.w(TAG, "No response from client for onStopJob "
+                        + getRunningJobNameLocked());
                 closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping");
                 break;
             case VERB_EXECUTING:
                 // Not an error - client ran out of time.
                 Slog.i(TAG, "Client timed out while executing (no jobFinished received), " +
-                        "sending onStop: "  +
-                        mRunningJob != null ? mRunningJob.toShortString() : "<null>");
+                        "sending onStop: " + getRunningJobNameLocked());
                 mParams.setStopReason(JobParameters.REASON_TIMEOUT);
                 sendStopMessageLocked("timeout while executing");
                 break;
             default:
-                Slog.e(TAG, "Handling timeout for an invalid job state: " +
-                        mRunningJob != null ? mRunningJob.toShortString() : "<null>"
-                        + ", dropping.");
+                Slog.e(TAG, "Handling timeout for an invalid job state: "
+                        + getRunningJobNameLocked() + ", dropping.");
                 closeAndCleanupJobLocked(false /* needsReschedule */, "invalid timeout");
         }
     }
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 922df1e..3795b7f 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -18,9 +18,7 @@
 
 import com.android.internal.util.DumpUtils;
 import com.android.server.Watchdog;
-import com.android.server.media.AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener;
 
-import android.Manifest;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -96,9 +94,10 @@
     private final ArrayMap<IBinder, ClientRecord> mAllClientRecords =
             new ArrayMap<IBinder, ClientRecord>();
     private int mCurrentUserId = -1;
-    private boolean mHasBluetoothRoute = false;
+    private boolean mGlobalBluetoothA2dpOn = false;
     private final IAudioService mAudioService;
     private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+    private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
 
     public MediaRouterService(Context context) {
         mContext = context;
@@ -137,13 +136,39 @@
             audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() {
                 @Override
                 public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
-                    mHasBluetoothRoute = newRoutes.bluetoothName != null;
+                    synchronized (mLock) {
+                        if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) {
+                            if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET
+                                    | AudioRoutesInfo.MAIN_HEADPHONES
+                                    | AudioRoutesInfo.MAIN_USB)) == 0) {
+                                // headset was plugged out.
+                                mGlobalBluetoothA2dpOn = newRoutes.bluetoothName != null;
+                            } else {
+                                // headset was plugged in.
+                                mGlobalBluetoothA2dpOn = false;
+                            }
+                            mCurAudioRoutesInfo.mainType = newRoutes.mainType;
+                        }
+                        if (!TextUtils.equals(
+                                newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
+                            if (newRoutes.bluetoothName == null) {
+                                // BT was disconnected.
+                                mGlobalBluetoothA2dpOn = false;
+                            } else {
+                                // BT was connected or changed.
+                                mGlobalBluetoothA2dpOn = true;
+                            }
+                            mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
+                        }
+                    }
                 }
             });
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException in the audio service.");
         }
-        mHasBluetoothRoute = (audioRoutes != null && audioRoutes.bluetoothName != null);
+        synchronized (mLock) {
+            mGlobalBluetoothA2dpOn = (audioRoutes != null && audioRoutes.bluetoothName != null);
+        }
     }
 
     public void systemRunning() {
@@ -246,6 +271,14 @@
 
     // Binder call
     @Override
+    public boolean isGlobalBluetoothA2doOn() {
+        synchronized (mLock) {
+            return mGlobalBluetoothA2dpOn;
+        }
+    }
+
+    // Binder call
+    @Override
     public void setDiscoveryRequest(IMediaRouterClient client,
             int routeTypes, boolean activeScan) {
         if (client == null) {
@@ -346,7 +379,12 @@
 
     void restoreBluetoothA2dp() {
         try {
-            mAudioService.setBluetoothA2dpOn(mHasBluetoothRoute);
+            boolean a2dpOn = false;
+            synchronized (mLock) {
+                a2dpOn = mGlobalBluetoothA2dpOn;
+            }
+            Slog.v(TAG, "restoreBluetoothA2dp( " + a2dpOn + ")");
+            mAudioService.setBluetoothA2dpOn(a2dpOn);
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
         }
@@ -354,12 +392,14 @@
 
     void restoreRoute(int uid) {
         ClientRecord clientRecord = null;
-        UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid));
-        if (userRecord != null && userRecord.mClientRecords != null) {
-            for (ClientRecord cr : userRecord.mClientRecords) {
-                if (validatePackageName(uid, cr.mPackageName)) {
-                    clientRecord = cr;
-                    break;
+        synchronized (mLock) {
+            UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid));
+            if (userRecord != null && userRecord.mClientRecords != null) {
+                for (ClientRecord cr : userRecord.mClientRecords) {
+                    if (validatePackageName(uid, cr.mPackageName)) {
+                        clientRecord = cr;
+                        break;
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 0778d09..07ab417 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -369,11 +369,14 @@
     private final boolean mSuppressDefaultPolicy;
 
     /** Defined network policies. */
+    @GuardedBy("mNetworkPoliciesSecondLock")
     final ArrayMap<NetworkTemplate, NetworkPolicy> mNetworkPolicy = new ArrayMap<>();
 
     /** Map from subId to subscription plans. */
+    @GuardedBy("mNetworkPoliciesSecondLock")
     final SparseArray<SubscriptionPlan[]> mSubscriptionPlans = new SparseArray<>();
     /** Map from subId to package name that owns subscription plans. */
+    @GuardedBy("mNetworkPoliciesSecondLock")
     final SparseArray<String> mSubscriptionPlansOwner = new SparseArray<>();
 
     /** Defined UID policies. */
@@ -2767,20 +2770,18 @@
             return plans.toArray(new SubscriptionPlan[plans.size()]);
         }
 
-        synchronized (mUidRulesFirstLock) {
-            synchronized (mNetworkPoliciesSecondLock) {
-                // Only give out plan details to the package that defined them,
-                // so that we don't risk leaking plans between apps. We always
-                // let in core system components (like the Settings app).
-                final String ownerPackage = mSubscriptionPlansOwner.get(subId);
-                if (Objects.equals(ownerPackage, callingPackage)
-                        || (UserHandle.getCallingAppId() == android.os.Process.SYSTEM_UID)) {
-                    return mSubscriptionPlans.get(subId);
-                } else {
-                    Log.w(TAG, "Not returning plans because caller " + callingPackage
-                            + " doesn't match owner " + ownerPackage);
-                    return null;
-                }
+        synchronized (mNetworkPoliciesSecondLock) {
+            // Only give out plan details to the package that defined them,
+            // so that we don't risk leaking plans between apps. We always
+            // let in core system components (like the Settings app).
+            final String ownerPackage = mSubscriptionPlansOwner.get(subId);
+            if (Objects.equals(ownerPackage, callingPackage)
+                    || (UserHandle.getCallingAppId() == android.os.Process.SYSTEM_UID)) {
+                return mSubscriptionPlans.get(subId);
+            } else {
+                Log.w(TAG, "Not returning plans because caller " + callingPackage
+                        + " doesn't match owner " + ownerPackage);
+                return null;
             }
         }
     }
diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java
index 8837c15..4ceb592 100644
--- a/services/core/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java
@@ -177,6 +177,34 @@
         }
     }
 
+    /**
+     * Safely multiple a value by a rational.
+     * <p>
+     * Internally it uses integer-based math whenever possible, but switches
+     * over to double-based math if values would overflow.
+     */
+    @VisibleForTesting
+    public static long multiplySafe(long value, long num, long den) {
+        long x = value;
+        long y = num;
+
+        // Logic shamelessly borrowed from Math.multiplyExact()
+        long r = x * y;
+        long ax = Math.abs(x);
+        long ay = Math.abs(y);
+        if (((ax | ay) >>> 31 != 0)) {
+            // Some bits greater than 2^31 that might cause overflow
+            // Check the result using the divide operator
+            // and check for the special case of Long.MIN_VALUE * -1
+            if (((y != 0) && (r / y != x)) ||
+                    (x == Long.MIN_VALUE && y == -1)) {
+                // Use double math to avoid overflowing
+                return (long) (((double) num / den) * value);
+            }
+        }
+        return r / den;
+    }
+
     public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
         return getRelevantUids(accessLevel, Binder.getCallingUid());
     }
@@ -274,8 +302,8 @@
             final long rawRxBytes = entry.rxBytes;
             final long rawTxBytes = entry.txBytes;
             final long targetBytes = augmentPlan.getDataUsageBytes();
-            final long targetRxBytes = (rawRxBytes * targetBytes) / rawBytes;
-            final long targetTxBytes = (rawTxBytes * targetBytes) / rawBytes;
+            final long targetRxBytes = multiplySafe(targetBytes, rawRxBytes, rawBytes);
+            final long targetTxBytes = multiplySafe(targetBytes, rawTxBytes, rawBytes);
 
             // Scale all matching buckets to reach anchor target
             final long beforeTotal = combined.getTotalBytes();
@@ -283,8 +311,8 @@
                 combined.getValues(i, entry);
                 if (entry.bucketStart >= augmentStart
                         && entry.bucketStart + entry.bucketDuration <= augmentEnd) {
-                    entry.rxBytes = (entry.rxBytes * targetRxBytes) / rawRxBytes;
-                    entry.txBytes = (entry.txBytes * targetTxBytes) / rawTxBytes;
+                    entry.rxBytes = multiplySafe(targetRxBytes, entry.rxBytes, rawRxBytes);
+                    entry.txBytes = multiplySafe(targetTxBytes, entry.txBytes, rawTxBytes);
                     // We purposefully clear out packet counters to indicate
                     // that this data has been augmented.
                     entry.rxPackets = 0;
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 4bd927d..3af5265 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -124,6 +124,7 @@
 import android.util.TrustedTime;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.net.VpnInfo;
 import com.android.internal.util.ArrayUtils;
@@ -241,12 +242,17 @@
     private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
             new DropBoxNonMonotonicObserver();
 
+    @GuardedBy("mStatsLock")
     private NetworkStatsRecorder mDevRecorder;
+    @GuardedBy("mStatsLock")
     private NetworkStatsRecorder mXtRecorder;
+    @GuardedBy("mStatsLock")
     private NetworkStatsRecorder mUidRecorder;
+    @GuardedBy("mStatsLock")
     private NetworkStatsRecorder mUidTagRecorder;
 
     /** Cached {@link #mXtRecorder} stats. */
+    @GuardedBy("mStatsLock")
     private NetworkStatsCollection mXtStatsCached;
 
     /** Current counter sets for each UID. */
@@ -328,15 +334,15 @@
             return;
         }
 
-        // create data recorders along with historical rotators
-        mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false);
-        mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false);
-        mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false);
-        mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true);
-
-        updatePersistThresholds();
-
         synchronized (mStatsLock) {
+            // create data recorders along with historical rotators
+            mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false);
+            mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false);
+            mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false);
+            mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true);
+
+            updatePersistThresholdsLocked();
+
             // upgrade any legacy stats, migrating them to rotated files
             maybeUpgradeLegacyStatsLocked();
 
@@ -674,9 +680,12 @@
             int flags, int fields, @NetworkStatsAccess.Level int accessLevel, int callingUid) {
         // We've been using pure XT stats long enough that we no longer need to
         // splice DEV and XT together.
-        return mXtStatsCached.getHistory(template, resolveSubscriptionPlan(template, flags),
-                UID_ALL, SET_ALL, TAG_NONE, fields, Long.MIN_VALUE, Long.MAX_VALUE,
-                accessLevel, callingUid);
+        final SubscriptionPlan augmentPlan = resolveSubscriptionPlan(template, flags);
+        synchronized (mStatsLock) {
+            return mXtStatsCached.getHistory(template, augmentPlan,
+                    UID_ALL, SET_ALL, TAG_NONE, fields, Long.MIN_VALUE, Long.MAX_VALUE,
+                    accessLevel, callingUid);
+        }
     }
 
     @Override
@@ -813,7 +822,7 @@
         synchronized (mStatsLock) {
             if (!mSystemReady) return;
 
-            updatePersistThresholds();
+            updatePersistThresholdsLocked();
 
             mDevRecorder.maybePersistLocked(currentTime);
             mXtRecorder.maybePersistLocked(currentTime);
@@ -869,7 +878,7 @@
      * reflect current {@link #mPersistThreshold} value. Always defers to
      * {@link Global} values when defined.
      */
-    private void updatePersistThresholds() {
+    private void updatePersistThresholdsLocked() {
         mDevRecorder.setPersistThreshold(mSettings.getDevPersistBytes(mPersistThreshold));
         mXtRecorder.setPersistThreshold(mSettings.getXtPersistBytes(mPersistThreshold));
         mUidRecorder.setPersistThreshold(mSettings.getUidPersistBytes(mPersistThreshold));
@@ -1311,7 +1320,7 @@
         synchronized (mStatsLock) {
             if (args.length > 0 && "--proto".equals(args[0])) {
                 // In this case ignore all other arguments.
-                dumpProto(fd);
+                dumpProtoLocked(fd);
                 return;
             }
 
@@ -1387,7 +1396,7 @@
         }
     }
 
-    private void dumpProto(FileDescriptor fd) {
+    private void dumpProtoLocked(FileDescriptor fd) {
         final ProtoOutputStream proto = new ProtoOutputStream(fd);
 
         // TODO Right now it writes all history.  Should it limit to the "since-boot" log?
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 638523a..27159fa 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -7817,13 +7817,13 @@
             case HapticFeedbackConstants.VIRTUAL_KEY:
                 return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
             case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
-                return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+                return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false);
             case HapticFeedbackConstants.KEYBOARD_PRESS:  // == HapticFeedbackConstants.KEYBOARD_TAP
                 return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
             case HapticFeedbackConstants.KEYBOARD_RELEASE:
-                return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+                return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false);
             case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
-                return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+                return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false);
             default:
                 return null;
         }
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 805250a..6484a13 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -429,6 +429,8 @@
         }
 
         public void getMagnificationRegionLocked(Region outMagnificationRegion) {
+            // Make sure we're working with the most current bounds
+            mMagnifedViewport.recomputeBoundsLocked();
             mMagnifedViewport.getMagnificationRegionLocked(outMagnificationRegion);
         }
 
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index c2edc04..c76b905 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -450,7 +450,7 @@
         return isAnimating;
     }
 
-    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+    void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken);
         pw.print(prefix); pw.print("mAnimator="); pw.println(mAnimator);
         pw.print(prefix); pw.print("freezingScreen="); pw.print(freezingScreen);
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index f142ff6..b083aee 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -366,7 +366,6 @@
                 // if made visible again.
                 wtoken.removeDeadWindows();
                 wtoken.setVisibleBeforeClientHidden();
-                mService.mUnknownAppVisibilityController.appRemovedOrHidden(wtoken);
             } else {
                 if (!mService.mAppTransition.isTransitionSet()
                         && mService.mAppTransition.isReady()) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index f4ac961..0a7d1c1 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -368,10 +368,10 @@
 
             boolean runningAppAnimation = false;
 
+            if (mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
+                mAppAnimator.setNullAnimation();
+            }
             if (transit != AppTransition.TRANSIT_UNSET) {
-                if (mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
-                    mAppAnimator.setNullAnimation();
-                }
                 if (mService.applyAnimationLocked(this, lp, transit, visible, isVoiceInteraction)) {
                     delayed = runningAppAnimation = true;
                 }
@@ -1759,6 +1759,9 @@
         if (mRemovingFromDisplay) {
             pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay);
         }
+        if (mAppAnimator.isAnimating()) {
+            mAppAnimator.dump(pw, prefix + "  ");
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index cff2fad..7953ee4 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -21,7 +21,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.animation.AnimationHandler;
-import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
@@ -32,13 +31,11 @@
 import android.os.Debug;
 import android.util.ArrayMap;
 import android.util.Slog;
-import android.view.Choreographer;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.view.WindowManagerInternal;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -142,9 +139,6 @@
         // True if this this animation was canceled and will be replaced the another animation from
         // the same {@link #BoundsAnimationTarget} target.
         private boolean mSkipFinalResize;
-        // True if this animation replaced a previous animation of the same
-        // {@link #BoundsAnimationTarget} target.
-        private final boolean mSkipAnimationStart;
         // True if this animation was canceled by the user, not as a part of a replacing animation
         private boolean mSkipAnimationEnd;
 
@@ -159,6 +153,7 @@
 
         // Whether to schedule PiP mode changes on animation start/end
         private @SchedulePipModeChangedState int mSchedulePipModeChangedState;
+        private @SchedulePipModeChangedState int mPrevSchedulePipModeChangedState;
 
         // Depending on whether we are animating from
         // a smaller to a larger size
@@ -171,14 +166,14 @@
 
         BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to,
                 @SchedulePipModeChangedState int schedulePipModeChangedState,
-                boolean moveFromFullscreen, boolean moveToFullscreen,
-                boolean replacingExistingAnimation) {
+                @SchedulePipModeChangedState int prevShedulePipModeChangedState,
+                boolean moveFromFullscreen, boolean moveToFullscreen) {
             super();
             mTarget = target;
             mFrom.set(from);
             mTo.set(to);
-            mSkipAnimationStart = replacingExistingAnimation;
             mSchedulePipModeChangedState = schedulePipModeChangedState;
+            mPrevSchedulePipModeChangedState = prevShedulePipModeChangedState;
             mMoveFromFullscreen = moveFromFullscreen;
             mMoveToFullscreen = moveToFullscreen;
             addUpdateListener(this);
@@ -200,7 +195,7 @@
         @Override
         public void onAnimationStart(Animator animation) {
             if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
-                    + " mSkipAnimationStart=" + mSkipAnimationStart
+                    + " mPrevSchedulePipModeChangedState=" + mPrevSchedulePipModeChangedState
                     + " mSchedulePipModeChangedState=" + mSchedulePipModeChangedState);
             mFinishAnimationAfterTransition = false;
             mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth,
@@ -210,18 +205,26 @@
             // running
             updateBooster();
 
-            // Ensure that we have prepared the target for animation before
-            // we trigger any size changes, so it can swap surfaces
-            // in to appropriate modes, or do as it wishes otherwise.
-            if (!mSkipAnimationStart) {
+            // Ensure that we have prepared the target for animation before we trigger any size
+            // changes, so it can swap surfaces in to appropriate modes, or do as it wishes
+            // otherwise.
+            if (mPrevSchedulePipModeChangedState == NO_PIP_MODE_CHANGED_CALLBACKS) {
                 mTarget.onAnimationStart(mSchedulePipModeChangedState ==
-                        SCHEDULE_PIP_MODE_CHANGED_ON_START);
+                        SCHEDULE_PIP_MODE_CHANGED_ON_START, false /* forceUpdate */);
 
                 // When starting an animation from fullscreen, pause here and wait for the
                 // windows-drawn signal before we start the rest of the transition down into PiP.
                 if (mMoveFromFullscreen) {
                     pause();
                 }
+            } else if (mPrevSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END &&
+                    mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
+                // We are replacing a running animation into PiP, but since it hasn't completed, the
+                // client will not currently receive any picture-in-picture mode change callbacks.
+                // However, we still need to report to them that they are leaving PiP, so this will
+                // force an update via a mode changed callback.
+                mTarget.onAnimationStart(true /* schedulePipModeChangedCallback */,
+                        true /* forceUpdate */);
             }
 
             // Immediately update the task bounds if they have to become larger, but preserve
@@ -388,6 +391,8 @@
             boolean moveFromFullscreen, boolean moveToFullscreen) {
         final BoundsAnimator existing = mRunningAnimations.get(target);
         final boolean replacing = existing != null;
+        @SchedulePipModeChangedState int prevSchedulePipModeChangedState =
+                NO_PIP_MODE_CHANGED_CALLBACKS;
 
         if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
                 + " schedulePipModeChangedState=" + schedulePipModeChangedState
@@ -403,6 +408,9 @@
                 return existing;
             }
 
+            // Save the previous state
+            prevSchedulePipModeChangedState = existing.mSchedulePipModeChangedState;
+
             // Update the PiP callback states if we are replacing the animation
             if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
                 if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
@@ -428,7 +436,8 @@
             existing.cancel();
         }
         final BoundsAnimator animator = new BoundsAnimator(target, from, to,
-                schedulePipModeChangedState, moveFromFullscreen, moveToFullscreen, replacing);
+                schedulePipModeChangedState, prevSchedulePipModeChangedState,
+                moveFromFullscreen, moveToFullscreen);
         mRunningAnimations.put(target, animator);
         animator.setFloatValues(0f, 1f);
         animator.setDuration((animationDuration != -1 ? animationDuration
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
index 8b1bf7b..647a2d6 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
@@ -31,7 +31,7 @@
      * @param schedulePipModeChangedCallback whether or not to schedule the PiP mode changed
      * callbacks
      */
-    void onAnimationStart(boolean schedulePipModeChangedCallback);
+    void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate);
 
     /**
      * Sets the size of the target (without any intermediate steps, like scheduling animation)
diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
index 989e8f2..fc0baad 100644
--- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
@@ -202,10 +202,12 @@
      */
 
     /** Calls directly into activity manager so window manager lock shouldn't held. */
-    public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {
+    public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
+            boolean forceUpdate) {
         if (mListener != null) {
             PinnedStackWindowListener listener = (PinnedStackWindowListener) mListener;
-            listener.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds);
+            listener.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds,
+                    forceUpdate);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowListener.java b/services/core/java/com/android/server/wm/PinnedStackWindowListener.java
index 12b9c1f..33e8a60 100644
--- a/services/core/java/com/android/server/wm/PinnedStackWindowListener.java
+++ b/services/core/java/com/android/server/wm/PinnedStackWindowListener.java
@@ -28,5 +28,6 @@
      * Called when the stack container pinned stack animation will change the picture-in-picture
      * mode. This is a direct call into ActivityManager.
      */
-    default void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {}
+    default void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
+            boolean forceUpdate) {}
 }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index a2c9283..7b047a8 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -53,7 +53,7 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
     private static final String SNAPSHOTS_DIRNAME = "snapshots";
     private static final String REDUCED_POSTFIX = "_reduced";
-    static final float REDUCED_SCALE = 0.5f;
+    static final float REDUCED_SCALE = ActivityManager.isLowRamDeviceStatic() ? 0.6f : 0.5f;
     static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
     private static final long DELAY_MS = 100;
     private static final int QUALITY = 95;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 39878cc..55151a1 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1498,7 +1498,7 @@
     }
 
     @Override  // AnimatesBounds
-    public void onAnimationStart(boolean schedulePipModeChangedCallback) {
+    public void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) {
         // Hold the lock since this is called from the BoundsAnimator running on the UiThread
         synchronized (mService.mWindowMap) {
             mBoundsAnimatingRequested = false;
@@ -1523,9 +1523,11 @@
             final PinnedStackWindowController controller =
                     (PinnedStackWindowController) getController();
             if (schedulePipModeChangedCallback && controller != null) {
-                // We need to schedule the PiP mode change after the animation down, so use the
-                // final bounds
-                controller.updatePictureInPictureModeForPinnedStackAnimation(null);
+                // We need to schedule the PiP mode change before the animation up. It is possible
+                // in this case for the animation down to not have been completed, so always
+                // force-schedule and update to the client to ensure that it is notified that it
+                // is no longer in picture-in-picture mode
+                controller.updatePictureInPictureModeForPinnedStackAnimation(null, forceUpdate);
             }
         }
     }
@@ -1553,7 +1555,7 @@
                 // We need to schedule the PiP mode change after the animation down, so use the
                 // final bounds
                 controller.updatePictureInPictureModeForPinnedStackAnimation(
-                        mBoundsAnimationTarget);
+                        mBoundsAnimationTarget, false /* forceUpdate */);
             }
 
             if (finalStackSize != null) {
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 0370490..d2f374d 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -153,9 +153,9 @@
     if (status == Status::OK) {
         return lengthMs;
     } else if (status != Status::UNSUPPORTED_OPERATION) {
-        // Don't warn on UNSUPPORTED_OPERATION, that's a normal even and just means the motor
-        // doesn't have a pre-defined waveform to perform for it, so we should just fall back
-        // to the framework waveforms.
+        // Don't warn on UNSUPPORTED_OPERATION, that's a normal event and just means the motor
+        // doesn't have a pre-defined waveform to perform for it, so we should just give the
+        // opportunity to fall back to the framework waveforms.
         ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32
                 ", error=%" PRIu32 ").", static_cast<int64_t>(effect),
                 static_cast<int32_t>(strength), static_cast<uint32_t>(status));
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 221b9b4..c828acc 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1331,11 +1331,13 @@
                     traceEnd();
                 }
 
-                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_VOICE_RECOGNIZERS)) {
-                    traceBeginAndSlog("StartVoiceRecognitionManager");
-                    mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
-                    traceEnd();
-                }
+                // We need to always start this service, regardless of whether the
+                // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
+                // of initializing various settings.  It will internally modify its behavior
+                // based on that feature.
+                traceBeginAndSlog("StartVoiceRecognitionManager");
+                mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
+                traceEnd();
 
                 if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
                     traceBeginAndSlog("StartGestureLauncher");
diff --git a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index 9d32496..0081214 100644
--- a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -126,6 +126,7 @@
         boolean mMovedToFullscreen;
         boolean mAnimationStarted;
         boolean mSchedulePipModeChangedOnStart;
+        boolean mForcePipModeChangedCallback;
         boolean mAnimationEnded;
         Rect mAnimationEndFinalStackBounds;
         boolean mSchedulePipModeChangedOnEnd;
@@ -140,6 +141,7 @@
             mAnimationStarted = false;
             mAnimationEnded = false;
             mAnimationEndFinalStackBounds = null;
+            mForcePipModeChangedCallback = false;
             mSchedulePipModeChangedOnStart = false;
             mSchedulePipModeChangedOnEnd = false;
             mStackBounds = from;
@@ -148,10 +150,11 @@
         }
 
         @Override
-        public void onAnimationStart(boolean schedulePipModeChangedCallback) {
+        public void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) {
             mAwaitingAnimationStart = false;
             mAnimationStarted = true;
             mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback;
+            mForcePipModeChangedCallback = forceUpdate;
         }
 
         @Override
@@ -232,7 +235,7 @@
             return this;
         }
 
-        BoundsAnimationDriver restart(Rect to) {
+        BoundsAnimationDriver restart(Rect to, boolean expectStartedAndPipModeChangedCallback) {
             if (mAnimator == null) {
                 throw new IllegalArgumentException("Call start() to start a new animation");
             }
@@ -251,8 +254,15 @@
                 assertSame(oldAnimator, mAnimator);
             }
 
-            // No animation start for replacing animation
-            assertTrue(!mTarget.mAnimationStarted);
+            if (expectStartedAndPipModeChangedCallback) {
+                // Replacing animation with pending pip mode changed callback, ensure we update
+                assertTrue(mTarget.mAnimationStarted);
+                assertTrue(mTarget.mSchedulePipModeChangedOnStart);
+                assertTrue(mTarget.mForcePipModeChangedCallback);
+            } else {
+                // No animation start for replacing animation
+                assertTrue(!mTarget.mAnimationStarted);
+            }
             mTarget.mAnimationStarted = true;
             return this;
         }
@@ -467,7 +477,7 @@
         mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
                 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
                 .update(0.25f)
-                .restart(BOUNDS_FLOATING)
+                .restart(BOUNDS_FLOATING, false /* expectStartedAndPipModeChangedCallback */)
                 .end()
                 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
     }
@@ -478,7 +488,8 @@
         mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
                 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
                 .update(0.25f)
-                .restart(BOUNDS_SMALLER_FLOATING)
+                .restart(BOUNDS_SMALLER_FLOATING,
+                        false /* expectStartedAndPipModeChangedCallback */)
                 .end()
                 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
     }
@@ -486,10 +497,12 @@
     @UiThreadTest
     @Test
     public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() throws Exception {
+        // When animating from fullscreen and the animation is interruped, we expect the animation
+        // start callback to be made, with a forced pip mode change callback
         mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
                 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
                 .update(0.25f)
-                .restart(BOUNDS_FULL)
+                .restart(BOUNDS_FULL, true /* expectStartedAndPipModeChangedCallback */)
                 .end()
                 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
     }
@@ -512,7 +525,7 @@
         mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
                 .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
                 .update(0.25f)
-                .restart(BOUNDS_FULL)
+                .restart(BOUNDS_FULL, false /* expectStartedAndPipModeChangedCallback */)
                 .end()
                 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
     }
@@ -523,7 +536,8 @@
         mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
                 .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
                 .update(0.25f)
-                .restart(BOUNDS_SMALLER_FLOATING)
+                .restart(BOUNDS_SMALLER_FLOATING,
+                        false /* expectStartedAndPipModeChangedCallback */)
                 .end()
                 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
     }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 4ffacfd..1569ac3 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -183,7 +183,7 @@
         private final boolean mEnableService;
 
         VoiceInteractionManagerServiceStub() {
-            mEnableService = shouldEnableService(mContext.getResources());
+            mEnableService = shouldEnableService(mContext);
         }
 
         // TODO: VI Make sure the caller is the current user or profile
@@ -348,10 +348,15 @@
             }
         }
 
-        private boolean shouldEnableService(Resources res) {
-            // VoiceInteractionService should not be enabled on low ram devices unless it has the config flag.
-            return !ActivityManager.isLowRamDeviceStatic() ||
-                    getForceVoiceInteractionServicePackage(res) != null;
+        private boolean shouldEnableService(Context context) {
+            // VoiceInteractionService should not be enabled on any low RAM devices
+            // or devices that have not declared the recognition feature, unless the
+            // device's configuration has explicitly set the config flag for a fixed
+            // voice interaction service.
+            return (!ActivityManager.isLowRamDeviceStatic()
+                            && context.getPackageManager().hasSystemFeature(
+                                    PackageManager.FEATURE_VOICE_RECOGNIZERS)) ||
+                    getForceVoiceInteractionServicePackage(context.getResources()) != null;
         }
 
         private String getForceVoiceInteractionServicePackage(Resources res) {
diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
index 0c2ad38..9c10264 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -27,7 +27,10 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
+import static com.android.server.net.NetworkStatsCollection.multiplySafe;
+
 import android.content.res.Resources;
+import android.net.ConnectivityManager;
 import android.net.NetworkIdentity;
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
@@ -40,6 +43,7 @@
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.format.DateUtils;
+import android.util.RecurrenceRule;
 
 import com.android.frameworks.tests.net.R;
 
@@ -53,6 +57,9 @@
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.List;
@@ -70,14 +77,27 @@
     private static final long TIME_B = 1326110400000L; // UTC: Monday 9th January 2012 12:00:00 PM
     private static final long TIME_C = 1326132000000L; // UTC: Monday 9th January 2012 06:00:00 PM
 
+    private static Clock sOriginalClock;
+
     @Override
     public void setUp() throws Exception {
         super.setUp();
+        sOriginalClock = RecurrenceRule.sClock;
 
         // ignore any device overlay while testing
         NetworkTemplate.forceAllNetworkTypes();
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        RecurrenceRule.sClock = sOriginalClock;
+    }
+
+    private void setClock(Instant instant) {
+        RecurrenceRule.sClock = Clock.fixed(instant, ZoneId.systemDefault());
+    }
+
     public void testReadLegacyNetwork() throws Exception {
         final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
         stageFile(R.raw.netstats_v1, testFile);
@@ -238,6 +258,9 @@
         final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
         collection.readLegacyNetwork(testFile);
 
+        // We're in the future, but not that far off
+        setClock(Instant.parse("2012-06-01T00:00:00.00Z"));
+
         // Test a bunch of plans that should result in no augmentation
         final List<SubscriptionPlan> plans = new ArrayList<>();
 
@@ -416,6 +439,28 @@
         }
     }
 
+    public void testAugmentPlanGigantic() throws Exception {
+        // We're in the future, but not that far off
+        setClock(Instant.parse("2012-06-01T00:00:00.00Z"));
+
+        // Create a simple history with a ton of measured usage
+        final NetworkStatsCollection large = new NetworkStatsCollection(HOUR_IN_MILLIS);
+        final NetworkIdentitySet ident = new NetworkIdentitySet();
+        ident.add(new NetworkIdentity(ConnectivityManager.TYPE_MOBILE, -1, TEST_IMSI, null,
+                false, true));
+        large.recordData(ident, UID_ALL, SET_ALL, TAG_NONE, TIME_A, TIME_B,
+                new NetworkStats.Entry(12_730_893_164L, 1, 0, 0, 0));
+
+        // Verify untouched total
+        assertEquals(12_730_893_164L, getHistory(large, null, TIME_A, TIME_C).getTotalBytes());
+
+        // Verify anchor that might cause overflows
+        final SubscriptionPlan plan = SubscriptionPlan.Builder
+                .createRecurringMonthly(ZonedDateTime.parse("2012-01-09T00:00:00.00Z"))
+                .setDataUsage(4_939_212_390L, TIME_B).build();
+        assertEquals(4_939_212_386L, getHistory(large, plan, TIME_A, TIME_C).getTotalBytes());
+    }
+
     public void testRounding() throws Exception {
         final NetworkStatsCollection coll = new NetworkStatsCollection(HOUR_IN_MILLIS);
 
@@ -437,6 +482,25 @@
         assertEquals(TIME_A - HOUR_IN_MILLIS, coll.roundDown(TIME_A - 1));
     }
 
+    public void testMultiplySafe() {
+        assertEquals(25, multiplySafe(50, 1, 2));
+        assertEquals(100, multiplySafe(50, 2, 1));
+
+        assertEquals(-10, multiplySafe(30, -1, 3));
+        assertEquals(0, multiplySafe(30, 0, 3));
+        assertEquals(10, multiplySafe(30, 1, 3));
+        assertEquals(20, multiplySafe(30, 2, 3));
+        assertEquals(30, multiplySafe(30, 3, 3));
+        assertEquals(40, multiplySafe(30, 4, 3));
+
+        assertEquals(100_000_000_000L,
+                multiplySafe(300_000_000_000L, 10_000_000_000L, 30_000_000_000L));
+        assertEquals(100_000_000_010L,
+                multiplySafe(300_000_000_000L, 10_000_000_001L, 30_000_000_000L));
+        assertEquals(823_202_048L,
+                multiplySafe(4_939_212_288L, 2_121_815_528L, 12_730_893_165L));
+    }
+
     /**
      * Copy a {@link Resources#openRawResource(int)} into {@link File} for
      * testing purposes.