Merge "eUICC API error code cleanup." into oc-dr1-dev
diff --git a/Android.mk b/Android.mk
index 3750548..933ac62 100644
--- a/Android.mk
+++ b/Android.mk
@@ -357,6 +357,7 @@
 	core/java/android/view/IPinnedStackController.aidl \
 	core/java/android/view/IPinnedStackListener.aidl \
 	core/java/android/view/IRotationWatcher.aidl \
+	core/java/android/view/IWallpaperVisibilityListener.aidl \
 	core/java/android/view/IWindow.aidl \
 	core/java/android/view/IWindowFocusObserver.aidl \
 	core/java/android/view/IWindowId.aidl \
diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java
index 3665d1b..4ee38fe 100644
--- a/core/java/android/companion/BluetoothDeviceFilterUtils.java
+++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java
@@ -58,7 +58,7 @@
 
     static boolean matchesAddress(String deviceAddress, BluetoothDevice device) {
         final boolean result = deviceAddress == null
-                || (device == null || !deviceAddress.equals(device.getAddress()));
+                || (device != null && deviceAddress.equals(device.getAddress()));
         if (DEBUG) debugLogMatchResult(result, device, deviceAddress);
         return result;
     }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 9b0bab4..fdb0f2ba 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6852,7 +6852,7 @@
             ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
         }
         ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
-        ai.resourceDirs = state.resourceDirs;
+        ai.resourceDirs = state.overlayPaths;
     }
 
     public static ApplicationInfo generateApplicationInfo(Package p, int flags,
@@ -7000,6 +7000,7 @@
             return null;
         }
         if (!copyNeeded(flags, a.owner, state, a.metaData, userId)) {
+            updateApplicationInfo(a.info.applicationInfo, flags, state);
             return a.info;
         }
         // Make shallow copies so we can store the metadata safely
@@ -7088,6 +7089,7 @@
             return null;
         }
         if (!copyNeeded(flags, s.owner, state, s.metaData, userId)) {
+            updateApplicationInfo(s.info.applicationInfo, flags, state);
             return s.info;
         }
         // Make shallow copies so we can store the metadata safely
@@ -7183,6 +7185,7 @@
         if (!copyNeeded(flags, p.owner, state, p.metaData, userId)
                 && ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0
                         || p.info.uriPermissionPatterns == null)) {
+            updateApplicationInfo(p.info.applicationInfo, flags, state);
             return p.info;
         }
         // Make shallow copies so we can store the metadata safely
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 4e53914..470336c 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -55,7 +55,7 @@
     public ArraySet<String> disabledComponents;
     public ArraySet<String> enabledComponents;
 
-    public String[] resourceDirs;
+    public String[] overlayPaths;
 
     public PackageUserState() {
         installed = true;
@@ -83,8 +83,8 @@
         installReason = o.installReason;
         disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
         enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
-        resourceDirs =
-            o.resourceDirs == null ? null : Arrays.copyOf(o.resourceDirs, o.resourceDirs.length);
+        overlayPaths =
+            o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length);
     }
 
     /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 784ed7a..921d518 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6803,6 +6803,22 @@
         public static final String ASSIST_GESTURE_SENSITIVITY = "assist_gesture_sensitivity";
 
         /**
+         * Whether the assist gesture should silence alerts.
+         *
+         * @hide
+         */
+        public static final String ASSIST_GESTURE_SILENCE_ALERTS_ENABLED =
+                "assist_gesture_silence_alerts_enabled";
+
+        /**
+         * Whether the assist gesture should wake the phone.
+         *
+         * @hide
+         */
+        public static final String ASSIST_GESTURE_WAKE_ENABLED =
+                "assist_gesture_wake_enabled";
+
+        /**
          * Whether Assist Gesture Deferred Setup has been completed
          *
          * @hide
@@ -7096,6 +7112,9 @@
             AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,
             ASSIST_GESTURE_ENABLED,
             ASSIST_GESTURE_SENSITIVITY,
+            ASSIST_GESTURE_SETUP_COMPLETE,
+            ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
+            ASSIST_GESTURE_WAKE_ENABLED,
             VR_DISPLAY_MODE,
             NOTIFICATION_BADGING
         };
diff --git a/core/java/android/view/IWallpaperVisibilityListener.aidl b/core/java/android/view/IWallpaperVisibilityListener.aidl
new file mode 100644
index 0000000..349f984
--- /dev/null
+++ b/core/java/android/view/IWallpaperVisibilityListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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 android.view;
+
+/**
+ * Listener to be invoked when wallpaper visibility changes.
+ * {@hide}
+ */
+oneway interface IWallpaperVisibilityListener {
+    /**
+     * Method that will be invoked when wallpaper becomes visible or hidden.
+     * @param visible True if wallpaper is being displayed; false otherwise.
+     * @param displayId The id of the display where wallpaper visibility changed.
+     */
+    void onWallpaperVisibilityChanged(boolean visible, int displayId);
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 2b73c14..e576a0f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -39,6 +39,7 @@
 import android.view.IOnKeyguardExitResult;
 import android.view.IPinnedStackListener;
 import android.view.IRotationWatcher;
+import android.view.IWallpaperVisibilityListener;
 import android.view.IWindowSession;
 import android.view.IWindowSessionCallback;
 import android.view.KeyEvent;
@@ -256,6 +257,19 @@
     Bitmap screenshotWallpaper();
 
     /**
+     * Registers a wallpaper visibility listener.
+     * @return Current visibility.
+     */
+    boolean registerWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+        int displayId);
+
+    /**
+     * Remove a visibility watcher that was added using registerWallpaperVisibilityListener.
+     */
+    void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+        int displayId);
+
+    /**
      * Used only for assist -- request a screenshot of the current application.
      */
     boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver);
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index de4d03e..489de56 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -996,6 +996,9 @@
         observer.mNative = null;
     }
 
+    /** Not actually public - internal use only. This doc to make lint happy */
+    public static native void disableVsync();
+
     static native void setupShadersDiskCache(String cacheFile);
 
     private static native void nRotateProcessStatsBuffer();
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index 1e8207a..f712d5f 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -293,9 +293,19 @@
      * @param setSelected whether to set the specified day as selected
      */
     private void setDate(long timeInMillis, boolean animate, boolean setSelected) {
+        boolean dateClamped = false;
+        // Clamp the target day in milliseconds to the min or max if outside the range.
+        if (timeInMillis < mMinDate.getTimeInMillis()) {
+            timeInMillis = mMinDate.getTimeInMillis();
+            dateClamped = true;
+        } else if (timeInMillis > mMaxDate.getTimeInMillis()) {
+            timeInMillis = mMaxDate.getTimeInMillis();
+            dateClamped = true;
+        }
+
         getTempCalendarForTime(timeInMillis);
 
-        if (setSelected) {
+        if (setSelected || dateClamped) {
             mSelectedDay.setTimeInMillis(timeInMillis);
         }
 
@@ -353,13 +363,6 @@
     public void onRangeChanged() {
         mAdapter.setRange(mMinDate, mMaxDate);
 
-        // Clamp the selected day to the new min/max.
-        if (mSelectedDay.before(mMinDate)) {
-            mSelectedDay.setTimeInMillis(mMinDate.getTimeInMillis());
-        } else if (mSelectedDay.after(mMaxDate)) {
-            mSelectedDay.setTimeInMillis(mMaxDate.getTimeInMillis());
-        }
-
         // Changing the min/max date changes the selection position since we
         // don't really have stable IDs. Jumps immediately to the new position.
         setDate(mSelectedDay.getTimeInMillis(), false, false);
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index f4c18c3..1bee692 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -16,6 +16,7 @@
 
 package com.android.internal.app;
 
+import android.app.PendingIntent;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.os.ParcelUuid;
@@ -26,7 +27,6 @@
  */
 interface ISoundTriggerService {
 
-
     SoundTrigger.GenericSoundModel getSoundModel(in ParcelUuid soundModelId);
 
     void updateSoundModel(in SoundTrigger.GenericSoundModel soundModel);
@@ -36,8 +36,17 @@
     int startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback,
          in SoundTrigger.RecognitionConfig config);
 
-    /**
-     * Stops recognition.
-     */
     int stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
+
+    int loadGenericSoundModel(in SoundTrigger.GenericSoundModel soundModel);
+    int loadKeyphraseSoundModel(in SoundTrigger.KeyphraseSoundModel soundModel);
+
+    int startRecognitionForIntent(in ParcelUuid soundModelId, in PendingIntent callbackIntent,
+         in SoundTrigger.RecognitionConfig config);
+
+    int stopRecognitionForIntent(in ParcelUuid soundModelId);
+
+    int unloadSoundModel(in ParcelUuid soundModelId);
+
+    boolean isRecognitionActive(in ParcelUuid parcelUuid);
 }
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 96b443d..1f84061 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -25,6 +25,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Stream;
 
@@ -101,7 +102,7 @@
     /**
      * Returns the given list, or an immutable empty list if the provided list is null
      *
-     * This can be used to guaranty null-safety without paying the price of extra allocations
+     * This can be used to guarantee null-safety without paying the price of extra allocations
      *
      * @see Collections#emptyList
      */
@@ -110,6 +111,17 @@
     }
 
     /**
+     * Returns the given set, or an immutable empty set if the provided set is null
+     *
+     * This can be used to guarantee null-safety without paying the price of extra allocations
+     *
+     * @see Collections#emptySet
+     */
+    public static @NonNull <T> Set<T> emptyIfNull(@Nullable Set<T> cur) {
+        return cur == null ? Collections.emptySet() : cur;
+    }
+
+    /**
      * Returns the size of the given list, or 0 if the list is null
      */
     public static int size(@Nullable Collection<?> cur) {
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index de67c50..26b0034 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -29,6 +29,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <android-base/stringprintf.h>
 #include <binder/IInterface.h>
 #include <binder/IServiceManager.h>
 #include <binder/IPCThreadState.h>
@@ -194,10 +195,34 @@
         /*
          * It's an Error: Reraise the exception and ask the runtime to abort.
          */
+
+        // Try to get the exception string. Sometimes logcat isn't available,
+        // so try to add it to the abort message.
+        std::string exc_msg = "(Unknown exception message)";
+        {
+            ScopedLocalRef<jclass> exc_class(env, env->GetObjectClass(excep));
+            jmethodID method_id = env->GetMethodID(exc_class.get(),
+                                                   "toString",
+                                                   "()Ljava/lang/String;");
+            ScopedLocalRef<jstring> jstr(
+                    env,
+                    reinterpret_cast<jstring>(
+                            env->CallObjectMethod(excep, method_id)));
+            env->ExceptionClear();  // Just for good measure.
+            if (jstr.get() != nullptr) {
+                ScopedUtfChars jstr_utf(env, jstr.get());
+                exc_msg = jstr_utf.c_str();
+            }
+        }
+
         env->Throw(excep);
         ALOGE("java.lang.Error thrown during binder transaction (stack trace follows) : ");
         env->ExceptionDescribe();
-        env->FatalError("java.lang.Error thrown during binder transaction.");
+
+        std::string error_msg = base::StringPrintf(
+                "java.lang.Error thrown during binder transaction: %s",
+                exc_msg.c_str());
+        env->FatalError(error_msg.c_str());
     }
 
 bail:
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 2657140..286c1a2 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -926,6 +926,10 @@
     return createBitmap(env, bitmap.release(), android::bitmap::kBitmapCreateFlag_Mutable);
 }
 
+static void android_view_ThreadedRenderer_disableVsync(JNIEnv*, jclass) {
+    RenderProxy::disableVsync();
+}
+
 // ----------------------------------------------------------------------------
 // FrameMetricsObserver
 // ----------------------------------------------------------------------------
@@ -1024,6 +1028,7 @@
                 (void*)android_view_ThreadedRenderer_copySurfaceInto },
     { "nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;",
             (void*)android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode },
+    { "disableVsync", "()V", (void*)android_view_ThreadedRenderer_disableVsync },
 };
 
 int register_android_view_ThreadedRenderer(JNIEnv* env) {
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index d154730..df2b35b 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -32,7 +32,8 @@
 OffscreenBuffer* BakedOpRenderer::startTemporaryLayer(uint32_t width, uint32_t height) {
     LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
 
-    OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height);
+    OffscreenBuffer* buffer = mRenderState.layerPool().get(
+            mRenderState, width, height, mWideColorGamut);
     startRepaintLayer(buffer, Rect(width, height));
     return buffer;
 }
@@ -103,7 +104,8 @@
 OffscreenBuffer* BakedOpRenderer::copyToLayer(const Rect& area) {
     const uint32_t width = area.getWidth();
     const uint32_t height = area.getHeight();
-    OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height);
+    OffscreenBuffer* buffer = mRenderState.layerPool().get(
+            mRenderState, width, height, mWideColorGamut);
     if (!area.isEmpty() && width != 0 && height != 0) {
         mCaches.textureState().activateTexture(0);
         mCaches.textureState().bindTexture(buffer->texture.id());
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index 4d76a3d..01ca367 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -54,12 +54,13 @@
         uint8_t spotShadowAlpha;
     };
 
-    BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque,
+    BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, bool wideColorGamut,
             const LightInfo& lightInfo)
             : mGlopReceiver(DefaultGlopReceiver)
             , mRenderState(renderState)
             , mCaches(caches)
             , mOpaque(opaque)
+            , mWideColorGamut(wideColorGamut)
             , mLightInfo(lightInfo) {
     }
 
@@ -118,6 +119,7 @@
     RenderState& mRenderState;
     Caches& mCaches;
     bool mOpaque;
+    bool mWideColorGamut;
     bool mHasDrawn = false;
 
     // render target state - setup by start/end layer/frame
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index aad81df..b587248 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -69,6 +69,7 @@
 bool Properties::forceDrawFrame = false;
 
 bool Properties::filterOutTestOverhead = false;
+bool Properties::disableVsync = false;
 
 static int property_get_int(const char* key, int defaultValue) {
     char buf[PROPERTY_VALUE_MAX] = {'\0',};
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 9db6449..91b4a2d 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -318,6 +318,12 @@
     // any overhead they add
     static bool filterOutTestOverhead;
 
+    // Workaround a device lockup in edge cases by switching to async mode
+    // instead of the default vsync (b/38372997). Only system_server should hit this.
+    // Any existing RenderProxy & Surface combination will be unaffected, only things
+    // created after changing this.
+    static bool disableVsync;
+
     // Used for testing only to change the render pipeline.
 #ifdef HWUI_GLES_WRAP_ENABLED
     static void overrideRenderPipelineType(RenderPipelineType);
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp
index 959059f..4ef31d5 100644
--- a/libs/hwui/Texture.cpp
+++ b/libs/hwui/Texture.cpp
@@ -120,6 +120,10 @@
 void Texture::upload(GLint internalFormat, uint32_t width, uint32_t height,
         GLenum format, GLenum type, const void* pixels) {
     GL_CHECKPOINT(MODERATE);
+
+    // We don't have color space information, we assume the data is gamma encoded
+    mIsLinear = false;
+
     bool needsAlloc = updateLayout(width, height, internalFormat, format, GL_TEXTURE_2D);
     if (!mId) {
         glGenTextures(1, &mId);
@@ -309,11 +313,16 @@
     bool rgba16fNeedsConversion = bitmap.colorType() == kRGBA_F16_SkColorType
             && internalFormat != GL_RGBA16F;
 
+    // RGBA16F is always linear extended sRGB
+    if (internalFormat == GL_RGBA16F) {
+        mIsLinear = true;
+    }
+
     mConnector.reset();
 
-    // RGBA16F is always extended sRGB, alpha masks don't have color profiles
+    // Alpha masks don't have color profiles
     // If an RGBA16F bitmap needs conversion, we know the target will be sRGB
-    if (internalFormat != GL_RGBA16F && internalFormat != GL_ALPHA && !rgba16fNeedsConversion) {
+    if (!mIsLinear && internalFormat != GL_ALPHA && !rgba16fNeedsConversion) {
         SkColorSpace* colorSpace = bitmap.info().colorSpace();
         // If the bitmap is sRGB we don't need conversion
         if (colorSpace != nullptr && !colorSpace->isSRGB()) {
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 55b74ed..7f742e6 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -88,7 +88,8 @@
      * The image data is undefined after calling this.
      */
     void resize(uint32_t width, uint32_t height, GLint internalFormat, GLint format) {
-        upload(internalFormat, width, height, format, GL_UNSIGNED_BYTE, nullptr);
+        upload(internalFormat, width, height, format,
+                internalFormat == GL_RGBA16F ? GL_HALF_FLOAT : GL_UNSIGNED_BYTE, nullptr);
     }
 
     /**
@@ -155,7 +156,7 @@
      * Returns true if this texture uses a linear encoding format.
      */
     constexpr bool isLinear() const {
-        return mInternalFormat == GL_RGBA16F;
+        return mIsLinear;
     }
 
     /**
@@ -219,6 +220,9 @@
     GLenum mMinFilter = GL_NEAREST_MIPMAP_LINEAR;
     GLenum mMagFilter = GL_LINEAR;
 
+    // Indicates whether the content of the texture is in linear space
+    bool mIsLinear = false;
+
     Caches& mCaches;
 
     std::unique_ptr<ColorSpaceConnector> mConnector;
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 095d32e..6d5ef1d 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -61,7 +61,7 @@
         const SkRect& dirty,
         const FrameBuilder::LightGeometry& lightGeometry,
         LayerUpdateQueue* layerUpdateQueue,
-        const Rect& contentDrawBounds, bool opaque,
+        const Rect& contentDrawBounds, bool opaque, bool wideColorGamut,
         const BakedOpRenderer::LightInfo& lightInfo,
         const std::vector<sp<RenderNode>>& renderNodes,
         FrameInfoVisualizer* profiler) {
@@ -85,7 +85,8 @@
             mRenderThread.getGrContext(), renderTargetDesc, &props));
 
     SkiaPipeline::updateLighting(lightGeometry, lightInfo);
-    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, wideColorGamut,
+            contentDrawBounds, surface);
     layerUpdateQueue->clear();
 
     // Draw visual debugging features
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index d5471de..aa29c8e 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -35,7 +35,7 @@
     bool draw(const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
             const FrameBuilder::LightGeometry& lightGeometry,
             LayerUpdateQueue* layerUpdateQueue,
-            const Rect& contentDrawBounds, bool opaque,
+            const Rect& contentDrawBounds, bool opaque, bool wideColorGamut,
             const BakedOpRenderer::LightInfo& lightInfo,
             const std::vector< sp<RenderNode> >& renderNodes,
             FrameInfoVisualizer* profiler) override;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index bbbbd5c..0bab793 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -72,16 +72,18 @@
 }
 
 void SkiaPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
-        LayerUpdateQueue* layerUpdateQueue, bool opaque,
+        LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
         const BakedOpRenderer::LightInfo& lightInfo) {
     updateLighting(lightGeometry, lightInfo);
     ATRACE_NAME("draw layers");
     renderVectorDrawableCache();
-    renderLayersImpl(*layerUpdateQueue, opaque);
+    renderLayersImpl(*layerUpdateQueue, opaque, wideColorGamut);
     layerUpdateQueue->clear();
 }
 
-void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
+void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers,
+        bool opaque, bool wideColorGamut) {
+    // TODO: Handle wide color gamut
     // Render all layers that need to be updated, in order.
     for (size_t i = 0; i < layers.entries().size(); i++) {
         RenderNode* layerNode = layers.entries()[i].renderNode.get();
@@ -129,12 +131,13 @@
 }
 
 bool SkiaPipeline::createOrUpdateLayer(RenderNode* node,
-        const DamageAccumulator& damageAccumulator) {
+        const DamageAccumulator& damageAccumulator, bool wideColorGamut) {
     SkSurface* layer = node->getLayerSurface();
     if (!layer || layer->width() != node->getWidth() || layer->height() != node->getHeight()) {
         SkImageInfo info = SkImageInfo::MakeN32Premul(node->getWidth(), node->getHeight());
         SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
         SkASSERT(mRenderThread.getGrContext() != nullptr);
+        // TODO: Handle wide color gamut requests
         node->setLayerSurface(
                 SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes,
                         info, 0, &props));
@@ -203,13 +206,13 @@
 }
 
 void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
-        const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds,
-        sk_sp<SkSurface> surface) {
+        const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut,
+        const Rect &contentDrawBounds, sk_sp<SkSurface> surface) {
 
     renderVectorDrawableCache();
 
     // draw all layers up front
-    renderLayersImpl(layers, opaque);
+    renderLayersImpl(layers, opaque, wideColorGamut);
 
     // initialize the canvas for the current frame
     SkCanvas* canvas = surface->getCanvas();
@@ -227,7 +230,7 @@
         }
     }
 
-    renderFrameImpl(layers, clip, nodes, opaque, contentDrawBounds, canvas);
+    renderFrameImpl(layers, clip, nodes, opaque, wideColorGamut, contentDrawBounds, canvas);
 
     if (skpCaptureEnabled() && recordingPicture) {
         sk_sp<SkPicture> picture = recorder->finishRecordingAsPicture();
@@ -260,8 +263,8 @@
 }
 
 void SkiaPipeline::renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip,
-        const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds,
-        SkCanvas* canvas) {
+        const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut,
+        const Rect &contentDrawBounds, SkCanvas* canvas) {
     SkAutoCanvasRestore saver(canvas, true);
     canvas->androidFramework_setDeviceClipRestriction(clip.roundOut());
 
@@ -388,7 +391,7 @@
     // each time a pixel would have been drawn.
     // Pass true for opaque so we skip the clear - the overdrawCanvas is already zero
     // initialized.
-    renderFrameImpl(layers, clip, nodes, true, contentDrawBounds, &overdrawCanvas);
+    renderFrameImpl(layers, clip, nodes, true, false, contentDrawBounds, &overdrawCanvas);
     sk_sp<SkImage> counts = offscreen->makeImageSnapshot();
 
     // Draw overdraw colors to the canvas.  The color filter will convert counts to colors.
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 6f5e719..19ffc46 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -39,15 +39,15 @@
     void unpinImages() override;
 
     void renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
-            LayerUpdateQueue* layerUpdateQueue, bool opaque,
+            LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
             const BakedOpRenderer::LightInfo& lightInfo) override;
 
     bool createOrUpdateLayer(RenderNode* node,
-            const DamageAccumulator& damageAccumulator) override;
+            const DamageAccumulator& damageAccumulator, bool wideColorGamut) override;
 
     void renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
-            const std::vector< sp<RenderNode> >& nodes, bool opaque, const Rect &contentDrawBounds,
-            sk_sp<SkSurface> surface);
+            const std::vector< sp<RenderNode> >& nodes, bool opaque, bool wideColorGamut,
+            const Rect &contentDrawBounds, sk_sp<SkSurface> surface);
 
     std::vector<VectorDrawableRoot*>* getVectorDrawables() { return &mVectorDrawables; }
 
@@ -55,7 +55,7 @@
 
     static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
 
-    static void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque);
+    static void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, bool wideColorGamut);
 
     static bool skpCaptureEnabled() { return false; }
 
@@ -110,8 +110,8 @@
 
 private:
     void renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip,
-            const std::vector< sp<RenderNode> >& nodes, bool opaque, const Rect &contentDrawBounds,
-            SkCanvas* canvas);
+            const std::vector< sp<RenderNode> >& nodes, bool opaque, bool wideColorGamut,
+            const Rect &contentDrawBounds, SkCanvas* canvas);
 
     /**
      *  Debugging feature.  Draws a semi-transparent overlay on each pixel, indicating
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index f1ef9e6..e1ef71f7 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -66,7 +66,7 @@
         const SkRect& dirty,
         const FrameBuilder::LightGeometry& lightGeometry,
         LayerUpdateQueue* layerUpdateQueue,
-        const Rect& contentDrawBounds, bool opaque,
+        const Rect& contentDrawBounds, bool opaque, bool wideColorGamut,
         const BakedOpRenderer::LightInfo& lightInfo,
         const std::vector<sp<RenderNode>>& renderNodes,
         FrameInfoVisualizer* profiler) {
@@ -76,7 +76,8 @@
         return false;
     }
     SkiaPipeline::updateLighting(lightGeometry, lightInfo);
-    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer);
+    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, wideColorGamut,
+            contentDrawBounds, backBuffer);
     layerUpdateQueue->clear();
 
     // Draw visual debugging features
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index 3481312..263206d 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -33,7 +33,7 @@
     bool draw(const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
             const FrameBuilder::LightGeometry& lightGeometry,
             LayerUpdateQueue* layerUpdateQueue,
-            const Rect& contentDrawBounds, bool opaque,
+            const Rect& contentDrawBounds, bool opaque, bool wideColorGamut,
             const BakedOpRenderer::LightInfo& lightInfo,
             const std::vector< sp<RenderNode> >& renderNodes,
             FrameInfoVisualizer* profiler) override;
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp
index a9bbb27..90b27c8 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.cpp
+++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp
@@ -35,17 +35,19 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
-        uint32_t viewportWidth, uint32_t viewportHeight)
+        uint32_t viewportWidth, uint32_t viewportHeight, bool wideColorGamut)
         : GpuMemoryTracker(GpuObjectType::OffscreenBuffer)
         , renderState(renderState)
         , viewportWidth(viewportWidth)
         , viewportHeight(viewportHeight)
-        , texture(caches) {
+        , texture(caches)
+        , wideColorGamut(wideColorGamut) {
     uint32_t width = computeIdealDimension(viewportWidth);
     uint32_t height = computeIdealDimension(viewportHeight);
     ATRACE_FORMAT("Allocate %ux%u HW Layer", width, height);
     caches.textureState().activateTexture(0);
-    texture.resize(width, height, caches.rgbaInternalFormat(), GL_RGBA);
+    texture.resize(width, height,
+            wideColorGamut ? GL_RGBA16F : caches.rgbaInternalFormat(), GL_RGBA);
     texture.blend = true;
     texture.setWrap(GL_CLAMP_TO_EDGE);
     // not setting filter on texture, since it's set when drawing, based on transform
@@ -127,7 +129,10 @@
     int deltaInt = int(lhs.width) - int(rhs.width);
     if (deltaInt != 0) return deltaInt;
 
-    return int(lhs.height) - int(rhs.height);
+    deltaInt = int(lhs.height) - int(rhs.height);
+    if (deltaInt != 0) return deltaInt;
+
+    return int(lhs.wideColorGamut) - int(rhs.wideColorGamut);
 }
 
 void OffscreenBufferPool::clear() {
@@ -139,10 +144,10 @@
 }
 
 OffscreenBuffer* OffscreenBufferPool::get(RenderState& renderState,
-        const uint32_t width, const uint32_t height) {
+        const uint32_t width, const uint32_t height, bool wideColorGamut) {
     OffscreenBuffer* layer = nullptr;
 
-    Entry entry(width, height);
+    Entry entry(width, height, wideColorGamut);
     auto iter = mPool.find(entry);
 
     if (iter != mPool.end()) {
@@ -154,7 +159,8 @@
         layer->viewportHeight = height;
         mSize -= layer->getSizeInBytes();
     } else {
-        layer = new OffscreenBuffer(renderState, Caches::getInstance(), width, height);
+        layer = new OffscreenBuffer(renderState, Caches::getInstance(),
+                width, height, wideColorGamut);
     }
 
     return layer;
@@ -174,7 +180,7 @@
         return layer;
     }
     putOrDelete(layer);
-    return get(renderState, width, height);
+    return get(renderState, width, height, layer->wideColorGamut);
 }
 
 void OffscreenBufferPool::dump() {
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h
index 26d4e36..d9422c9 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.h
+++ b/libs/hwui/renderstate/OffscreenBufferPool.h
@@ -43,7 +43,7 @@
 class OffscreenBuffer : GpuMemoryTracker {
 public:
     OffscreenBuffer(RenderState& renderState, Caches& caches,
-            uint32_t viewportWidth, uint32_t viewportHeight);
+            uint32_t viewportWidth, uint32_t viewportHeight, bool wideColorGamut = false);
     ~OffscreenBuffer();
 
     Rect getTextureCoordinates();
@@ -68,6 +68,8 @@
     uint32_t viewportHeight;
     Texture texture;
 
+    bool wideColorGamut = false;
+
     // Portion of layer that has been drawn to. Used to minimize drawing area when
     // drawing back to screen / parent FBO.
     Region region;
@@ -90,7 +92,7 @@
     ~OffscreenBufferPool();
 
     WARN_UNUSED_RESULT OffscreenBuffer* get(RenderState& renderState,
-            const uint32_t width, const uint32_t height);
+            const uint32_t width, const uint32_t height, bool wideColorGamut = false);
 
     WARN_UNUSED_RESULT OffscreenBuffer* resize(OffscreenBuffer* layer,
             const uint32_t width, const uint32_t height);
@@ -122,14 +124,16 @@
     struct Entry {
         Entry() {}
 
-        Entry(const uint32_t layerWidth, const uint32_t layerHeight)
+        Entry(const uint32_t layerWidth, const uint32_t layerHeight, bool wideColorGamut)
                 : width(OffscreenBuffer::computeIdealDimension(layerWidth))
-                , height(OffscreenBuffer::computeIdealDimension(layerHeight)) {}
+                , height(OffscreenBuffer::computeIdealDimension(layerHeight))
+                , wideColorGamut(wideColorGamut) {}
 
         explicit Entry(OffscreenBuffer* layer)
                 : layer(layer)
                 , width(layer->texture.width())
-                , height(layer->texture.height()) {
+                , height(layer->texture.height())
+                , wideColorGamut(layer->wideColorGamut) {
         }
 
         static int compare(const Entry& lhs, const Entry& rhs);
@@ -149,6 +153,7 @@
         OffscreenBuffer* layer = nullptr;
         uint32_t width = 0;
         uint32_t height = 0;
+        bool wideColorGamut = false;
     }; // struct Entry
 
     std::multiset<Entry> mPool;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index a79bf35..7799248 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -421,7 +421,7 @@
     SkRect windowDirty = computeDirtyRect(frame, &dirty);
 
     bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
-            mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes, &(profiler()));
+            mContentDrawBounds, mOpaque, mWideColorGamut, mLightInfo, mRenderNodes, &(profiler()));
 
     waitOnFences();
 
@@ -563,7 +563,8 @@
     // purposes when the frame is actually drawn
     node->setPropertyFieldsDirty(RenderNode::GENERIC);
 
-    mRenderPipeline->renderLayers(mLightGeometry, &mLayerUpdateQueue, mOpaque, mLightInfo);
+    mRenderPipeline->renderLayers(mLightGeometry, &mLayerUpdateQueue,
+            mOpaque, mWideColorGamut, mLightInfo);
 
     node->incStrong(nullptr);
     mPrefetchedLayers.insert(node);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 76623f9..b1f4050 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -76,7 +76,7 @@
      *  @return true if the layer has been created or updated
      */
     bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& dmgAccumulator) {
-        return mRenderPipeline->createOrUpdateLayer(node, dmgAccumulator);
+        return mRenderPipeline->createOrUpdateLayer(node, dmgAccumulator, mWideColorGamut);
     }
 
     /**
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index ecf686c..d6240e7 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -357,6 +357,9 @@
         }
     }
     mCurrentSurface = surface;
+    if (Properties::disableVsync) {
+        eglSwapInterval(mEglDisplay, 0);
+    }
     return true;
 }
 
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 46ac0d2..f9b6e38 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -59,7 +59,7 @@
     virtual bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
             const FrameBuilder::LightGeometry& lightGeometry,
             LayerUpdateQueue* layerUpdateQueue,
-            const Rect& contentDrawBounds, bool opaque,
+            const Rect& contentDrawBounds, bool opaque, bool wideColorGamut,
             const BakedOpRenderer::LightInfo& lightInfo,
             const std::vector< sp<RenderNode> >& renderNodes,
             FrameInfoVisualizer* profiler) = 0;
@@ -73,11 +73,11 @@
     virtual bool isContextReady() = 0;
     virtual void onDestroyHardwareResources() = 0;
     virtual void renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
-            LayerUpdateQueue* layerUpdateQueue, bool opaque,
+            LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
             const BakedOpRenderer::LightInfo& lightInfo) = 0;
     virtual TaskManager* getTaskManager() = 0;
     virtual bool createOrUpdateLayer(RenderNode* node,
-            const DamageAccumulator& damageAccumulator) = 0;
+            const DamageAccumulator& damageAccumulator, bool wideColorGamut) = 0;
     virtual bool pinImages(std::vector<SkImage*>& mutableImages) = 0;
     virtual bool pinImages(LsaVector<sk_sp<Bitmap>>& images) = 0;
     virtual void unpinImages() = 0;
diff --git a/libs/hwui/renderthread/OpenGLPipeline.cpp b/libs/hwui/renderthread/OpenGLPipeline.cpp
index e15d0eb..7283eb1 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.cpp
+++ b/libs/hwui/renderthread/OpenGLPipeline.cpp
@@ -58,7 +58,7 @@
 bool OpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
         const FrameBuilder::LightGeometry& lightGeometry,
         LayerUpdateQueue* layerUpdateQueue,
-        const Rect& contentDrawBounds, bool opaque,
+        const Rect& contentDrawBounds, bool opaque, bool wideColorGamut,
         const BakedOpRenderer::LightInfo& lightInfo,
         const std::vector< sp<RenderNode> >& renderNodes,
         FrameInfoVisualizer* profiler) {
@@ -77,7 +77,7 @@
     frameBuilder.deferRenderNodeScene(renderNodes, contentDrawBounds);
 
     BakedOpRenderer renderer(caches, mRenderThread.renderState(),
-            opaque, lightInfo);
+            opaque, wideColorGamut, lightInfo);
     frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
     ProfileRenderer profileRenderer(renderer);
     profiler->draw(profileRenderer);
@@ -184,14 +184,14 @@
 }
 
 void OpenGLPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
-        LayerUpdateQueue* layerUpdateQueue, bool opaque,
+        LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
         const BakedOpRenderer::LightInfo& lightInfo) {
     static const std::vector< sp<RenderNode> > emptyNodeList;
     auto& caches = Caches::getInstance();
     FrameBuilder frameBuilder(*layerUpdateQueue, lightGeometry, caches);
     layerUpdateQueue->clear();
-    BakedOpRenderer renderer(caches, mRenderThread.renderState(),
-            opaque, lightInfo);
+    // TODO: Handle wide color gamut contexts
+    BakedOpRenderer renderer(caches, mRenderThread.renderState(), opaque, wideColorGamut, lightInfo);
     LOG_ALWAYS_FATAL_IF(renderer.didDraw(), "shouldn't draw in buildlayer case");
     frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
 }
@@ -205,12 +205,13 @@
 }
 
 bool OpenGLPipeline::createOrUpdateLayer(RenderNode* node,
-        const DamageAccumulator& damageAccumulator) {
+        const DamageAccumulator& damageAccumulator, bool wideColorGamut) {
     RenderState& renderState = mRenderThread.renderState();
     OffscreenBufferPool& layerPool = renderState.layerPool();
     bool transformUpdateNeeded = false;
     if (node->getLayer() == nullptr) {
-        node->setLayer(layerPool.get(renderState, node->getWidth(), node->getHeight()));
+        node->setLayer(layerPool.get(renderState,
+                node->getWidth(), node->getHeight(), wideColorGamut));
         transformUpdateNeeded = true;
     } else if (!layerMatchesWH(node->getLayer(), node->getWidth(), node->getHeight())) {
         // TODO: remove now irrelevant, currently enqueued damage (respecting damage ordering)
diff --git a/libs/hwui/renderthread/OpenGLPipeline.h b/libs/hwui/renderthread/OpenGLPipeline.h
index 0e8c3f5..4ca19fb 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.h
+++ b/libs/hwui/renderthread/OpenGLPipeline.h
@@ -36,7 +36,7 @@
     bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
             const FrameBuilder::LightGeometry& lightGeometry,
             LayerUpdateQueue* layerUpdateQueue,
-            const Rect& contentDrawBounds, bool opaque,
+            const Rect& contentDrawBounds, bool opaque, bool wideColorGamut,
             const BakedOpRenderer::LightInfo& lightInfo,
             const std::vector< sp<RenderNode> >& renderNodes,
             FrameInfoVisualizer* profiler) override;
@@ -50,11 +50,11 @@
     bool isContextReady() override;
     void onDestroyHardwareResources() override;
     void renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
-            LayerUpdateQueue* layerUpdateQueue, bool opaque,
+            LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
             const BakedOpRenderer::LightInfo& lightInfo) override;
     TaskManager* getTaskManager() override;
     bool createOrUpdateLayer(RenderNode* node,
-            const DamageAccumulator& damageAccumulator) override;
+            const DamageAccumulator& damageAccumulator, bool wideColorGamut) override;
     bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
     bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override;
     void unpinImages() override;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index ec56c31..80c2955 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -18,6 +18,7 @@
 
 #include "DeferredLayerUpdater.h"
 #include "DisplayList.h"
+#include "Properties.h"
 #include "Readback.h"
 #include "Rect.h"
 #include "renderthread/CanvasContext.h"
@@ -709,6 +710,10 @@
     thread.queue(task);
 }
 
+void RenderProxy::disableVsync() {
+    Properties::disableVsync = true;
+}
+
 void RenderProxy::post(RenderTask* task) {
     mRenderThread.queue(task);
 }
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index e1e2808..31f0ce6 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -138,6 +138,8 @@
     static int copyGraphicBufferInto(GraphicBuffer* buffer, SkBitmap* bitmap);
 
     static void onBitmapDestroyed(uint32_t pixelRefId);
+
+    ANDROID_API static void disableVsync();
 private:
     RenderThread& mRenderThread;
     CanvasContext* mContext;
diff --git a/libs/hwui/tests/microbench/FrameBuilderBench.cpp b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
index 398e7a8..a5e85df 100644
--- a/libs/hwui/tests/microbench/FrameBuilderBench.cpp
+++ b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
@@ -83,7 +83,7 @@
                     sLightGeometry, caches);
             frameBuilder.deferRenderNode(*node);
 
-            BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
+            BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo);
             frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
             benchmark::DoNotOptimize(&renderer);
         }
@@ -142,7 +142,7 @@
                     sLightGeometry, Caches::getInstance());
             frameBuilder.deferRenderNode(*node);
 
-            BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
+            BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo);
             frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
             benchmark::DoNotOptimize(&renderer);
         }
diff --git a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
index c46592c..b0ef11f 100644
--- a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
@@ -37,7 +37,7 @@
 class ValidatingBakedOpRenderer : public BakedOpRenderer {
 public:
     ValidatingBakedOpRenderer(RenderState& renderState, std::function<void(const Glop& glop)> validator)
-            : BakedOpRenderer(Caches::getInstance(), renderState, true, sLightInfo)
+            : BakedOpRenderer(Caches::getInstance(), renderState, true, false, sLightInfo)
             , mValidator(validator) {
         mGlopReceiver = ValidatingGlopReceiver;
     }
diff --git a/libs/hwui/tests/unit/BakedOpRendererTests.cpp b/libs/hwui/tests/unit/BakedOpRendererTests.cpp
index 380062a..603599c 100644
--- a/libs/hwui/tests/unit/BakedOpRendererTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpRendererTests.cpp
@@ -24,7 +24,8 @@
 const BakedOpRenderer::LightInfo sLightInfo = { 128, 128 };
 
 RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, startRepaintLayer_clear) {
-    BakedOpRenderer renderer(Caches::getInstance(), renderThread.renderState(), true, sLightInfo);
+    BakedOpRenderer renderer(Caches::getInstance(), renderThread.renderState(),
+            true, false, sLightInfo);
     OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200u, 200u);
 
     layer.dirty(Rect(200, 200));
diff --git a/libs/hwui/tests/unit/LeakCheckTests.cpp b/libs/hwui/tests/unit/LeakCheckTests.cpp
index 6c42ca1..19d7ef5 100644
--- a/libs/hwui/tests/unit/LeakCheckTests.cpp
+++ b/libs/hwui/tests/unit/LeakCheckTests.cpp
@@ -45,7 +45,7 @@
     FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
             sLightGeometery, Caches::getInstance());
     frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-    BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
+    BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo);
     frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
 }
 
@@ -62,6 +62,6 @@
     FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
             sLightGeometery, Caches::getInstance());
     frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-    BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
+    BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo);
     frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
 }
diff --git a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
index 6cd595a..919852f 100644
--- a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
+++ b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
@@ -41,6 +41,19 @@
     EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes());
 }
 
+RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, constructWideColorGamut) {
+    OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 49u, 149u, true);
+    EXPECT_EQ(49u, layer.viewportWidth);
+    EXPECT_EQ(149u, layer.viewportHeight);
+
+    EXPECT_EQ(64u, layer.texture.width());
+    EXPECT_EQ(192u, layer.texture.height());
+
+    EXPECT_TRUE(layer.wideColorGamut);
+
+    EXPECT_EQ(64u * 192u * 8u, layer.getSizeInBytes());
+}
+
 RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, getTextureCoordinates) {
     OffscreenBuffer layerAligned(renderThread.renderState(), Caches::getInstance(), 256u, 256u);
     EXPECT_EQ(Rect(0, 1, 1, 0),
@@ -88,6 +101,47 @@
     EXPECT_EQ(0u, pool.getCount());
 }
 
+RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, getPutClearWideColorGamut) {
+    OffscreenBufferPool pool;
+
+    auto layer = pool.get(renderThread.renderState(), 100u, 200u, true);
+    EXPECT_EQ(100u, layer->viewportWidth);
+    EXPECT_EQ(200u, layer->viewportHeight);
+    EXPECT_TRUE(layer->wideColorGamut);
+
+    ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize());
+
+    pool.putOrDelete(layer);
+    ASSERT_EQ(layer->getSizeInBytes(), pool.getSize());
+
+    auto layer2 = pool.get(renderThread.renderState(), 102u, 202u, true);
+    EXPECT_EQ(layer, layer2) << "layer should be recycled";
+    ASSERT_EQ(0u, pool.getSize()) << "pool should have been emptied by removing only layer";
+
+    pool.putOrDelete(layer2);
+    EXPECT_EQ(1u, pool.getCount());
+    pool.clear();
+    EXPECT_EQ(0u, pool.getSize());
+    EXPECT_EQ(0u, pool.getCount());
+
+    // add non wide gamut layer
+    auto layer3 = pool.get(renderThread.renderState(), 100u, 200u);
+    EXPECT_FALSE(layer3->wideColorGamut);
+    pool.putOrDelete(layer3);
+    EXPECT_EQ(1u, pool.getCount());
+
+    auto layer4 = pool.get(renderThread.renderState(), 100u, 200u, true);
+    EXPECT_TRUE(layer4->wideColorGamut);
+    EXPECT_EQ(1u, pool.getCount());
+    ASSERT_NE(layer3, layer4);
+
+    pool.putOrDelete(layer4);
+
+    pool.clear();
+    EXPECT_EQ(0u, pool.getSize());
+    EXPECT_EQ(0u, pool.getCount());
+}
+
 RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, resize) {
     OffscreenBufferPool pool;
 
@@ -123,6 +177,43 @@
     pool.putOrDelete(layer2);
 }
 
+RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, resizeWideColorGamut) {
+    OffscreenBufferPool pool;
+
+    auto layer = pool.get(renderThread.renderState(), 64u, 64u, true);
+
+    // resize in place
+    ASSERT_EQ(layer, pool.resize(layer, 60u, 55u));
+    EXPECT_EQ(60u, layer->viewportWidth);
+    EXPECT_EQ(55u, layer->viewportHeight);
+    EXPECT_EQ(64u, layer->texture.width());
+    EXPECT_EQ(64u, layer->texture.height());
+
+    EXPECT_TRUE(layer->wideColorGamut);
+    EXPECT_EQ(64u * 64u * 8u, layer->getSizeInBytes());
+
+    // resized to use different object in pool
+    auto layer2 = pool.get(renderThread.renderState(), 128u, 128u, true);
+    pool.putOrDelete(layer2);
+    ASSERT_EQ(1u, pool.getCount());
+
+    // add a non-wide gamut layer
+    auto layer3 = pool.get(renderThread.renderState(), 128u, 128u);
+    pool.putOrDelete(layer3);
+    ASSERT_EQ(2u, pool.getCount());
+
+    ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u));
+    EXPECT_EQ(120u, layer2->viewportWidth);
+    EXPECT_EQ(125u, layer2->viewportHeight);
+    EXPECT_EQ(128u, layer2->texture.width());
+    EXPECT_EQ(128u, layer2->texture.height());
+
+    EXPECT_TRUE(layer2->wideColorGamut);
+    EXPECT_EQ(128u * 128u * 8u, layer2->getSizeInBytes());
+
+    pool.putOrDelete(layer2);
+}
+
 RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, putAndDestroy) {
     OffscreenBufferPool pool;
     // layer too big to return to the pool
@@ -153,3 +244,4 @@
 
     EXPECT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer));
 }
+
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 686d06f..4c3e182 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -450,7 +450,7 @@
     LayerUpdateQueue layerUpdateQueue;
     layerUpdateQueue.enqueueLayerWithDamage(child.get(),
             android::uirenderer::Rect(LAYER_WIDTH, LAYER_HEIGHT));
-    SkiaPipeline::renderLayersImpl(layerUpdateQueue, true);
+    SkiaPipeline::renderLayersImpl(layerUpdateQueue, true, false);
     EXPECT_EQ(1, drawCounter);  //assert index 0 is drawn on the layer
 
     RenderNodeDrawable drawable(parent.get(), surfaceLayer1->getCanvas(), true);
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index a895cba..b397b15 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -51,7 +51,8 @@
     auto surface = SkSurface::MakeRasterN32Premul(1, 1);
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes,
+            opaque, false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
 }
 
@@ -72,10 +73,12 @@
     auto surface = SkSurface::MakeRasterN32Premul(2, 2);
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes,
+            true, false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, false, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes,
+            false, false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned int)SK_ColorTRANSPARENT);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
 }
@@ -94,7 +97,8 @@
     auto surface = SkSurface::MakeRasterN32Premul(2, 2);
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes,
+            true, false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
     ASSERT_EQ(TestUtils::getColor(surface, 1, 0), SK_ColorBLUE);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorRED);
@@ -135,7 +139,7 @@
     lightGeometry.center = { 0.0f, 0.0f, 0.0f };
     BakedOpRenderer::LightInfo lightInfo;
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
-    pipeline->renderLayers(lightGeometry, &layerUpdateQueue, opaque, lightInfo);
+    pipeline->renderLayers(lightGeometry, &layerUpdateQueue, opaque, false, lightInfo);
     ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorRED);
     ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorBLUE);
     ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 1), SK_ColorWHITE);
@@ -166,32 +170,38 @@
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
 
     // Single draw, should be white.
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque,
+            false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
 
     // 1 Overdraw, should be blue blended onto white.
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque,
+            false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffd0d0ff);
 
     // 2 Overdraw, should be green blended onto white
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque,
+            false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffd0ffd0);
 
     // 3 Overdraw, should be pink blended onto white.
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque,
+            false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffffc0c0);
 
     // 4 Overdraw, should be red blended onto white.
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque,
+            false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffff8080);
 
     // 5 Overdraw, should be red blended onto white.
     renderNodes.push_back(whiteNode);
-    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque,
+            false, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffff8080);
 }
 
@@ -278,7 +288,7 @@
     SkRect dirty = SkRect::MakeWH(800, 600);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
     sk_sp<DeferLayer<DeferTestCanvas>> surface(new DeferLayer<DeferTestCanvas>());
-    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, contentDrawBounds, surface);
+    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, false, contentDrawBounds, surface);
     EXPECT_EQ(4, surface->canvas()->mDrawCounter);
 }
 
@@ -308,7 +318,7 @@
     SkRect dirty = SkRect::MakeLTRB(10, 20, 30, 40);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
     sk_sp<DeferLayer<ClippedTestCanvas>> surface(new DeferLayer<ClippedTestCanvas>());
-    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true,
+    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, false,
             SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface);
     EXPECT_EQ(1, surface->canvas()->mDrawCounter);
 }
@@ -339,7 +349,7 @@
     SkRect dirty = SkRect::MakeLTRB(10, 10, 40, 40);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
     sk_sp<DeferLayer<ClipReplaceTestCanvas>> surface(new DeferLayer<ClipReplaceTestCanvas>());
-    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true,
+    pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, false,
             SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface);
     EXPECT_EQ(1, surface->canvas()->mDrawCounter);
 }
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 7f8140a..92ffae0 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -15,7 +15,9 @@
  */
 
 package android.media.soundtrigger;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
 
+import android.app.PendingIntent;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -23,6 +25,10 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.SoundModel;
+import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.os.Handler;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
@@ -178,4 +184,144 @@
             return mGenericSoundModel;
         }
     }
+
+
+    /**
+     * Default message type.
+     * @hide
+     */
+    public static final int FLAG_MESSAGE_TYPE_UNKNOWN = -1;
+    /**
+     * Contents of EXTRA_MESSAGE_TYPE extra for a RecognitionEvent.
+     * @hide
+     */
+    public static final int FLAG_MESSAGE_TYPE_RECOGNITION_EVENT = 0;
+    /**
+     * Contents of EXTRA_MESSAGE_TYPE extra for recognition error events.
+     * @hide
+     */
+    public static final int FLAG_MESSAGE_TYPE_RECOGNITION_ERROR = 1;
+    /**
+     * Contents of EXTRA_MESSAGE_TYPE extra for a recognition paused events.
+     * @hide
+     */
+    public static final int FLAG_MESSAGE_TYPE_RECOGNITION_PAUSED = 2;
+    /**
+     * Contents of EXTRA_MESSAGE_TYPE extra for recognition resumed events.
+     * @hide
+     */
+    public static final int FLAG_MESSAGE_TYPE_RECOGNITION_RESUMED = 3;
+
+    /**
+     * Extra key in the intent for the type of the message.
+     * @hide
+     */
+    public static final String EXTRA_MESSAGE_TYPE = "android.media.soundtrigger.MESSAGE_TYPE";
+    /**
+     * Extra key in the intent that holds the RecognitionEvent parcelable.
+     * @hide
+     */
+    public static final String EXTRA_RECOGNITION_EVENT = "android.media.soundtrigger.RECOGNITION_EVENT";
+    /**
+     * Extra key in the intent that holds the status in an error message.
+     * @hide
+     */
+    public static final String EXTRA_STATUS = "android.media.soundtrigger.STATUS";
+
+    /**
+     * Loads a given sound model into the sound trigger. Note the model will be unloaded if there is
+     * an error/the system service is restarted.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public int loadSoundModel(SoundModel soundModel) {
+        if (soundModel == null) {
+            return STATUS_ERROR;
+        }
+
+        try {
+            switch (soundModel.type) {
+                case SoundModel.TYPE_GENERIC_SOUND:
+                    return mSoundTriggerService.loadGenericSoundModel(
+                            (GenericSoundModel) soundModel);
+                case SoundModel.TYPE_KEYPHRASE:
+                    return mSoundTriggerService.loadKeyphraseSoundModel(
+                            (KeyphraseSoundModel) soundModel);
+                default:
+                    Slog.e(TAG, "Unkown model type");
+                    return STATUS_ERROR;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Starts recognition on the given model id. All events from the model will be sent to the
+     * PendingIntent.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public int startRecognition(UUID soundModelId, PendingIntent callbackIntent,
+            RecognitionConfig config) {
+        if (soundModelId == null || callbackIntent == null || config == null) {
+            return STATUS_ERROR;
+        }
+        try {
+            return mSoundTriggerService.startRecognitionForIntent(new ParcelUuid(soundModelId),
+                    callbackIntent, config);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Stops the given model's recognition.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public int stopRecognition(UUID soundModelId) {
+        if (soundModelId == null) {
+            return STATUS_ERROR;
+        }
+        try {
+            return mSoundTriggerService.stopRecognitionForIntent(new ParcelUuid(soundModelId));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes the given model from memory. Will also stop any pending recognitions.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public int unloadSoundModel(UUID soundModelId) {
+        if (soundModelId == null) {
+            return STATUS_ERROR;
+        }
+        try {
+            return mSoundTriggerService.unloadSoundModel(
+                    new ParcelUuid(soundModelId));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns true if the given model has had detection started on it.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public boolean isRecognitionActive(UUID soundModelId) {
+        if (soundModelId == null) {
+            return false;
+        }
+        try {
+            return mSoundTriggerService.isRecognitionActive(
+                    new ParcelUuid(soundModelId));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index 2a4ab0f..3b29a6c 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -20,6 +20,8 @@
 import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
 
 import static com.android.internal.util.ArrayUtils.isEmpty;
+import static com.android.internal.util.CollectionUtils.emptyIfNull;
+import static com.android.internal.util.CollectionUtils.size;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -154,6 +156,25 @@
             onReadyToShowUI();
         }
 
+        // If filtering to get single device by mac address, also search in the set of already
+        // bonded devices to allow linking those directly
+        String singleMacAddressFilter = null;
+        if (mRequest.isSingleDevice()) {
+            int numFilters = size(mBluetoothFilters);
+            for (int i = 0; i < numFilters; i++) {
+                BluetoothDeviceFilter filter = mBluetoothFilters.get(i);
+                if (!TextUtils.isEmpty(filter.getAddress())) {
+                    singleMacAddressFilter = filter.getAddress();
+                    break;
+                }
+            }
+        }
+        if (singleMacAddressFilter != null) {
+            for (BluetoothDevice dev : emptyIfNull(mBluetoothAdapter.getBondedDevices())) {
+                onDeviceFound(DeviceFilterPair.findMatch(dev, mBluetoothFilters));
+            }
+        }
+
         if (shouldScan(mBluetoothFilters)) {
             final IntentFilter intentFilter = new IntentFilter();
             intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
@@ -211,6 +232,8 @@
     }
 
     private void onDeviceFound(@Nullable DeviceFilterPair device) {
+        if (device == null) return;
+
         if (mDevicesFound.contains(device)) {
             return;
         }
@@ -444,12 +467,9 @@
                 }
 
                 for (int i = 0; i < scanResults.size(); i++) {
-                    DeviceFilterPair<android.net.wifi.ScanResult> deviceFilterPair =
-                            DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters);
-                    if (deviceFilterPair != null) onDeviceFound(deviceFilterPair);
+                    onDeviceFound(DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters));
                 }
             }
-
         }
     }
 }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
index a36f583..17d820a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
@@ -426,7 +426,7 @@
             // be based off the start of the written ones instead of zero.
             // The written pages are always non-null and not empty.
             final int offset = -pagesWrittenToFile[0].getStart();
-            PageRangeUtils.offset(pagesInDocRequested, offset);
+            PageRangeUtils.offset(pagesInDocRequested.clone(), offset);
             return pagesInDocRequested;
         } else if (Arrays.equals(pagesInDocRequested, ALL_PAGES_RANGE)
                 && isAllPages(pagesWrittenToFile, pageCount)) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 474de90..1cbb745 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -88,21 +88,16 @@
     private static final String XMLTAG_TIMEZONE = "timezone";
 
     public static CharSequence getTimeZoneOffsetAndName(Context context, TimeZone tz, Date now) {
-        final Locale locale = Locale.getDefault();
-        final CharSequence gmtText = getGmtOffsetText(context, locale, tz, now);
-        final TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
-        final ZoneGetterData data = new ZoneGetterData(context);
-
-        final boolean useExemplarLocationForLocalNames =
-                shouldUseExemplarLocationForLocalNames(data, timeZoneNames);
-        final CharSequence zoneName = getTimeZoneDisplayName(data, timeZoneNames,
-                useExemplarLocationForLocalNames, tz, tz.getID());
-        if (zoneName == null) {
+        Locale locale = Locale.getDefault();
+        CharSequence gmtText = getGmtOffsetText(context, locale, tz, now);
+        TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
+        String zoneNameString = getZoneLongName(timeZoneNames, tz, now);
+        if (zoneNameString == null) {
             return gmtText;
         }
 
         // We don't use punctuation here to avoid having to worry about localizing that too!
-        return TextUtils.concat(gmtText, " ", zoneName);
+        return TextUtils.concat(gmtText, " ", zoneNameString);
     }
 
     public static List<Map<String, Object>> getZonesList(Context context) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 9d09737..e067de1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -34,6 +34,8 @@
     public static final String CATEGORY_SOUND = "com.android.settings.category.ia.sound";
     public static final String CATEGORY_STORAGE = "com.android.settings.category.ia.storage";
     public static final String CATEGORY_SECURITY = "com.android.settings.category.ia.security";
+    public static final String CATEGORY_SECURITY_LOCKSCREEN =
+            "com.android.settings.category.ia.lockscreen";
     public static final String CATEGORY_ACCOUNT = "com.android.settings.category.ia.accounts";
     public static final String CATEGORY_SYSTEM = "com.android.settings.category.ia.system";
     public static final String CATEGORY_SYSTEM_LANGUAGE =
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java
index 703e9d2..a3345ee 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java
@@ -47,9 +47,9 @@
     }
 
     @Test
-    public void getTimeZoneOffsetAndName_setLondon_returnLondon() {
-        // Check it will ends with 'London', not 'British Summer Time' or sth else
-        testTimeZoneOffsetAndNameInner(TIME_ZONE_LONDON_ID, "London");
+    public void getTimeZoneOffsetAndName_setLondon_returnBritishSummerTime() {
+        // Check it will ends with 'British Summer Time', not 'London' or sth else
+        testTimeZoneOffsetAndNameInner(TIME_ZONE_LONDON_ID, "British Summer Time");
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
index 40353e7..9fc8a96 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
@@ -54,13 +54,14 @@
         allKeys.add(CategoryKey.CATEGORY_SOUND);
         allKeys.add(CategoryKey.CATEGORY_STORAGE);
         allKeys.add(CategoryKey.CATEGORY_SECURITY);
+        allKeys.add(CategoryKey.CATEGORY_SECURITY_LOCKSCREEN);
         allKeys.add(CategoryKey.CATEGORY_ACCOUNT);
         allKeys.add(CategoryKey.CATEGORY_SYSTEM);
         allKeys.add(CategoryKey.CATEGORY_SYSTEM_LANGUAGE);
         allKeys.add(CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
         // DO NOT REMOVE ANYTHING ABOVE
 
-        assertThat(allKeys.size()).isEqualTo(13);
+        assertThat(allKeys.size()).isEqualTo(14);
     }
 
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index b777d41..06d00be 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -45,8 +45,8 @@
 
 import com.android.ims.ImsConfig;
 import com.android.internal.content.PackageHelper;
+import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.RILConstants;
-import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternView;
@@ -2617,9 +2617,9 @@
             loadSetting(stmt, Settings.Global.PREFERRED_NETWORK_MODE, type);
 
             // Set the preferred cdma subscription source to target desired value or default
-            // value defined in CdmaSubscriptionSourceManager
+            // value defined in Phone
             type = SystemProperties.getInt("ro.telephony.default_cdma_sub",
-                        CdmaSubscriptionSourceManager.PREFERRED_CDMA_SUBSCRIPTION);
+                        Phone.PREFERRED_CDMA_SUBSCRIPTION);
             loadSetting(stmt, Settings.Global.CDMA_SUBSCRIPTION_MODE, type);
 
             loadIntegerSetting(stmt, Settings.Global.LOW_BATTERY_SOUND_TIMEOUT,
diff --git a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java
index 4a5d8b4..2d794fb 100644
--- a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java
+++ b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.support.annotation.NonNull;
 import android.support.annotation.VisibleForTesting;
+import android.support.v4.graphics.ColorUtils;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -41,15 +42,12 @@
 
     private static final String TAG = "ColorExtractor";
 
-    @VisibleForTesting
-    static final int FALLBACK_COLOR = 0xff83888d;
+    public static final int FALLBACK_COLOR = 0xff83888d;
 
     private int mMainFallbackColor = FALLBACK_COLOR;
     private int mSecondaryFallbackColor = FALLBACK_COLOR;
     private final SparseArray<GradientColors[]> mGradientColors;
     private final ArrayList<OnColorsChangedListener> mOnColorsChangedListeners;
-    // Colors to return when the wallpaper isn't visible
-    private final GradientColors mWpHiddenColors;
     private final Context mContext;
     private final ExtractionType mExtractionType;
 
@@ -60,9 +58,6 @@
     @VisibleForTesting
     public ColorExtractor(Context context, ExtractionType extractionType) {
         mContext = context;
-        mWpHiddenColors = new GradientColors();
-        mWpHiddenColors.setMainColor(FALLBACK_COLOR);
-        mWpHiddenColors.setSecondaryColor(FALLBACK_COLOR);
         mExtractionType = extractionType;
 
         mGradientColors = new SparseArray<>();
@@ -123,7 +118,6 @@
         if (which != WallpaperManager.FLAG_LOCK && which != WallpaperManager.FLAG_SYSTEM) {
             throw new IllegalArgumentException("which should be FLAG_SYSTEM or FLAG_NORMAL");
         }
-
         return mGradientColors.get(which)[type];
     }
 
@@ -134,7 +128,6 @@
             GradientColors[] lockColors = mGradientColors.get(WallpaperManager.FLAG_LOCK);
             extractInto(colors, lockColors[TYPE_NORMAL], lockColors[TYPE_DARK],
                     lockColors[TYPE_EXTRA_DARK]);
-
             changed = true;
         }
         if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
@@ -149,7 +142,7 @@
         }
     }
 
-    private void triggerColorsChanged(int which) {
+    protected void triggerColorsChanged(int which) {
         for (OnColorsChangedListener listener: mOnColorsChangedListeners) {
             listener.onColorsChanged(this, which);
         }
@@ -258,4 +251,4 @@
     public interface OnColorsChangedListener {
         void onColorsChanged(ColorExtractor colorExtractor, int which);
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java b/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java
index fd698d0..b5f4a8c 100644
--- a/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java
+++ b/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java
@@ -39,7 +39,7 @@
 import org.junit.runner.RunWith;
 
 /**
- * Tests tonal palette generation.
+ * Tests color extraction generation.
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -101,14 +101,12 @@
         };
         ColorExtractor extractor = new ColorExtractor(mContext, type);
 
-        assertEquals("Extracted colors not being used!",
-                extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_NORMAL),
-                colorsExpectedNormal);
-        assertEquals("Extracted colors not being used!",
-                extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_DARK),
-                colorsExpectedDark);
-        assertEquals("Extracted colors not being used!",
-                extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_EXTRA_DARK),
-                colorsExpectedExtraDark);
+        GradientColors colors = extractor.getColors(WallpaperManager.FLAG_SYSTEM,
+                ColorExtractor.TYPE_NORMAL);
+        assertEquals("Extracted colors not being used!", colors, colorsExpectedNormal);
+        colors = extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_DARK);
+        assertEquals("Extracted colors not being used!", colors, colorsExpectedDark);
+        colors = extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_EXTRA_DARK);
+        assertEquals("Extracted colors not being used!", colors, colorsExpectedExtraDark);
     }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DozeServicePlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DozeServicePlugin.java
new file mode 100644
index 0000000..3ca5690
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DozeServicePlugin.java
@@ -0,0 +1,21 @@
+package com.android.systemui.plugins;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+@ProvidesInterface(action = DozeServicePlugin.ACTION, version = DozeServicePlugin.VERSION)
+public interface DozeServicePlugin extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_DOZE";
+    int VERSION = 1;
+
+    public interface RequestDoze {
+        void onRequestShowDoze();
+
+        void onRequestHideDoze();
+    }
+
+    void onDreamingStarted();
+
+    void onDreamingStopped();
+
+    void setDozeRequester(RequestDoze requester);
+}
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 9076199..18ffd0f 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -16,12 +16,12 @@
 <RelativeLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/volume_dialog"
-    android:layout_width="@dimen/volume_dialog_panel_width"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_marginBottom="@dimen/volume_dialog_margin_bottom"
-    android:layout_gravity="center_vertical|end"
+    android:background="@drawable/volume_dialog_background"
     android:paddingTop="@dimen/volume_dialog_padding_top"
-    android:translationZ="8dp" >
+    android:translationZ="4dp" >
 
     <LinearLayout
         android:id="@+id/volume_dialog_content"
@@ -57,7 +57,6 @@
             android:layout_height="wrap_content"
             android:ellipsize="end"
             android:maxLines="1"
-            android:visibility="gone"
             android:textAppearance="@style/TextAppearance.Volume.Header" />
         <com.android.keyguard.AlphaOptimizedImageButton
             xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/packages/SystemUI/res/layout/volume_dialog_wrapped.xml b/packages/SystemUI/res/layout/volume_dialog_wrapped.xml
deleted file mode 100644
index 57489fd..0000000
--- a/packages/SystemUI/res/layout/volume_dialog_wrapped.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<com.android.systemui.HardwareUiLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_marginTop="@dimen/top_padding"
-    android:layout_marginBottom="@dimen/bottom_padding">
-
-    <include layout="@layout/volume_dialog"/>
-
-</com.android.systemui.HardwareUiLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d3cbe4a..81ca230 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -262,6 +262,9 @@
     <!-- Doze: alpha to apply to small icons when dozing -->
     <integer name="doze_small_icon_alpha">222</integer><!-- 87% of 0xff -->
 
+    <!-- Doze: whether the double tap sensor reports 2D touch coordinates -->
+    <bool name="doze_double_tap_reports_touch_coordinates">false</bool>
+
     <!-- Hotspot tile: number of days to show after feature is used. -->
     <integer name="days_to_show_hotspot_tile">30</integer>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fc9e585..e3a84d3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -216,6 +216,8 @@
     <!-- The width of the panel that holds the quick settings. -->
     <dimen name="qs_panel_width">@dimen/notification_panel_width</dimen>
 
+    <dimen name="volume_dialog_panel_width">@dimen/standard_notification_panel_width</dimen>
+
     <!-- Gravity for the notification panel -->
     <integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->
 
@@ -800,7 +802,6 @@
 
     <dimen name="hwui_edge_margin">16dp</dimen>
 
-    <dimen name="volume_dialog_panel_width">315dp</dimen>
     <dimen name="global_actions_panel_width">125dp</dimen>
 
     <dimen name="global_actions_top_padding">100dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
index 6a86bb2..38811ac 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
@@ -21,6 +21,7 @@
 import android.view.View;
 import android.widget.Button;
 import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionInfo;
 import android.telephony.euicc.EuiccManager;
 
 import java.lang.ref.WeakReference;
@@ -58,8 +59,11 @@
     public static boolean isEsimLocked(Context context, int subId) {
         EuiccManager euiccManager =
                 (EuiccManager) context.getSystemService(Context.EUICC_SERVICE);
-        return euiccManager.isEnabled()
-                && SubscriptionManager.from(context).getActiveSubscriptionInfo(subId).isEmbedded();
+        if (!euiccManager.isEnabled()) {
+            return false;
+        }
+        SubscriptionInfo sub = SubscriptionManager.from(context).getActiveSubscriptionInfo(subId);
+        return  sub != null && sub.isEmbedded();
     }
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 73a2a43..1b694b3 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -28,6 +28,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.PluginDependencyProvider;
@@ -38,9 +39,9 @@
 import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
-import com.android.systemui.statusbar.phone.StatusBarWindowManager;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
+import com.android.systemui.statusbar.phone.StatusBarWindowManager;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -77,7 +78,6 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
-import com.android.systemui.tuner.TunablePadding;
 import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerServiceImpl;
@@ -86,8 +86,6 @@
 import com.android.systemui.util.leak.LeakReporter;
 import com.android.systemui.volume.VolumeDialogControllerImpl;
 
-import com.google.android.colorextraction.ColorExtractor;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.HashMap;
@@ -109,6 +107,7 @@
  * services, registered receivers, etc.
  */
 public class Dependency extends SystemUI {
+    private static final String TAG = "Dependency";
 
     /**
      * Key for getting a background Looper for background work.
@@ -268,7 +267,7 @@
         mProviders.put(AccessibilityManagerWrapper.class,
                 () -> new AccessibilityManagerWrapper(mContext));
 
-        mProviders.put(ColorExtractor.class, () -> new ColorExtractor(mContext));
+        mProviders.put(SysuiColorExtractor.class, () -> new SysuiColorExtractor(mContext));
 
         mProviders.put(TunablePaddingService.class, () -> new TunablePaddingService());
 
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
new file mode 100644
index 0000000..5f393d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -0,0 +1,134 @@
+/*
+ * 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.colorextraction;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Display;
+import android.view.IWallpaperVisibilityListener;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.android.colorextraction.ColorExtractor;
+import com.google.android.colorextraction.types.ExtractionType;
+import com.google.android.colorextraction.types.Tonal;
+
+/**
+ * ColorExtractor aware of wallpaper visibility
+ */
+public class SysuiColorExtractor extends ColorExtractor {
+    private static final String TAG = "SysuiColorExtractor";
+    private boolean mWallpaperVisible;
+    // Colors to return when the wallpaper isn't visible
+    private final GradientColors mWpHiddenColors;
+
+    public SysuiColorExtractor(Context context) {
+        this(context, new Tonal(), true);
+    }
+
+    @VisibleForTesting
+    public SysuiColorExtractor(Context context, ExtractionType type, boolean registerVisibility) {
+        super(context, type);
+
+        mWpHiddenColors = new GradientColors();
+        mWpHiddenColors.setMainColor(FALLBACK_COLOR);
+        mWpHiddenColors.setSecondaryColor(FALLBACK_COLOR);
+
+        if (registerVisibility) {
+            try {
+                IWindowManager windowManagerService = WindowManagerGlobal.getWindowManagerService();
+                Handler handler = Handler.getMain();
+                boolean visible = windowManagerService.registerWallpaperVisibilityListener(
+                        new IWallpaperVisibilityListener.Stub() {
+                            @Override
+                            public void onWallpaperVisibilityChanged(boolean newVisibility,
+                                    int displayId) throws RemoteException {
+                                handler.post(() -> setWallpaperVisible(newVisibility));
+                            }
+                        }, Display.DEFAULT_DISPLAY);
+                setWallpaperVisible(visible);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Can't listen to wallpaper visibility changes", e);
+            }
+        }
+    }
+
+    /**
+     * Get TYPE_NORMAL colors when wallpaper is visible, or fallback otherwise.
+     *
+     * @param which FLAG_LOCK or FLAG_SYSTEM
+     * @return colors
+     */
+    @Override
+    public GradientColors getColors(int which) {
+        return getColors(which, TYPE_NORMAL);
+    }
+
+    /**
+     * Wallpaper colors when the wallpaper is visible, fallback otherwise.
+     *
+     * @param which FLAG_LOCK or FLAG_SYSTEM
+     * @param type TYPE_NORMAL, TYPE_DARK or TYPE_EXTRA_DARK
+     * @return colors
+     */
+    @Override
+    public GradientColors getColors(int which, int type) {
+        return getColors(which, type, false /* ignoreVisibility */);
+    }
+
+    /**
+     * Get TYPE_NORMAL colors, possibly ignoring wallpaper visibility.
+     *
+     * @param which FLAG_LOCK or FLAG_SYSTEM
+     * @param ignoreWallpaperVisibility whether you want fallback colors or not if the wallpaper
+     *                                  isn't visible
+     * @return
+     */
+    public GradientColors getColors(int which, boolean ignoreWallpaperVisibility) {
+        return getColors(which, TYPE_NORMAL, ignoreWallpaperVisibility);
+    }
+
+    /**
+     *
+     * @param which FLAG_LOCK or FLAG_SYSTEM
+     * @param type TYPE_NORMAL, TYPE_DARK or TYPE_EXTRA_DARK
+     * @param ignoreWallpaperVisibility true if true wallpaper colors should be returning
+     *                                  if it's visible or not
+     * @return colors
+     */
+    public GradientColors getColors(int which, int type, boolean ignoreWallpaperVisibility) {
+        if (mWallpaperVisible || ignoreWallpaperVisibility) {
+            return super.getColors(which, type);
+        } else {
+            return mWpHiddenColors;
+        }
+    }
+
+    @VisibleForTesting
+    void setWallpaperVisible(boolean visible) {
+        if (mWallpaperVisible != visible) {
+            mWallpaperVisible = visible;
+            triggerColorsChanged(WallpaperManager.FLAG_SYSTEM);
+        }
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 3e424d0..5aaa6c7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -38,6 +38,8 @@
 
     void setAnimateWakeup(boolean animateWakeup);
 
+    void onDoubleTap(float x, float y);
+
     interface Callback {
         default void onNotificationHeadsUp() {}
         default void onPowerSaveChanged(boolean active) {}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 73f5222..23da716 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -81,17 +81,18 @@
                         mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION),
                         null /* setting */,
                         dozeParameters.getPulseOnSigMotion(),
-                        DozeLog.PULSE_REASON_SENSOR_SIGMOTION),
+                        DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */),
                 mPickupSensor = new TriggerSensor(
                         mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
                         Settings.Secure.DOZE_PULSE_ON_PICK_UP,
                         config.pulseOnPickupAvailable(),
-                        DozeLog.PULSE_REASON_SENSOR_PICKUP),
+                        DozeLog.PULSE_REASON_SENSOR_PICKUP, false /* touchCoords */),
                 new TriggerSensor(
                         findSensorWithType(config.doubleTapSensorType()),
                         Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
                         true /* configured */,
-                        DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP)
+                        DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
+                        dozeParameters.doubleTapReportsTouchCoordinates())
         };
 
         mProxSensor = new ProxSensor();
@@ -207,16 +208,19 @@
         final boolean mConfigured;
         final int mPulseReason;
         final String mSetting;
+        final boolean mReportsTouchCoordinates;
 
         private boolean mRequested;
         private boolean mRegistered;
         private boolean mDisabled;
 
-        public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason) {
+        public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
+                boolean reportsTouchCoordinates) {
             mSensor = sensor;
             mSetting = setting;
             mConfigured = configured;
             mPulseReason = pulseReason;
+            mReportsTouchCoordinates = reportsTouchCoordinates;
         }
 
         public void setListening(boolean listen) {
@@ -276,7 +280,13 @@
                 }
 
                 mRegistered = false;
-                mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck);
+                float screenX = -1;
+                float screenY = -1;
+                if (mReportsTouchCoordinates && event.values.length >= 2) {
+                    screenX = event.values[0];
+                    screenY = event.values[1];
+                }
+                mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck, screenX, screenY);
                 updateListener();  // reregister, this sensor only fires once
             }));
         }
@@ -309,7 +319,12 @@
          * Called when a sensor requests a pulse
          * @param pulseReason Requesting sensor, e.g. {@link DozeLog#PULSE_REASON_SENSOR_PICKUP}
          * @param sensorPerformedProxCheck true if the sensor already checked for FAR proximity.
+         * @param screenX the location on the screen where the sensor fired or -1
+         *                if the sensor doesn't support reporting screen locations.
+         * @param screenY the location on the screen where the sensor fired or -1
+         *                if the sensor doesn't support reporting screen locations.
          */
-        void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck);
+        void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck,
+                float screenX, float screenY);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 3271caf..d9fb087 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -16,23 +16,27 @@
 
 package com.android.systemui.doze;
 
+import android.content.Context;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.service.dreams.DreamService;
 import android.util.Log;
 
 import com.android.systemui.Dependency;
-import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.DozeServicePlugin;
 import com.android.systemui.plugins.PluginManager;
-
+import com.android.systemui.plugins.DozeServicePlugin.RequestDoze;
+import com.android.systemui.plugins.PluginListener;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-public class DozeService extends DreamService implements DozeMachine.Service {
+public class DozeService extends DreamService
+        implements DozeMachine.Service, RequestDoze, PluginListener<DozeServicePlugin> {
     private static final String TAG = "DozeService";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private DozeMachine mDozeMachine;
+    private DozeServicePlugin mDozePlugin;
 
     public DozeService() {
         setDebug(DEBUG);
@@ -48,23 +52,44 @@
             finish();
             return;
         }
-
+        Dependency.get(PluginManager.class).addPluginListener(this,
+                DozeServicePlugin.class, false /* Allow multiple */);
         mDozeMachine = new DozeFactory().assembleMachine(this);
     }
 
     @Override
+    public void onPluginConnected(DozeServicePlugin plugin, Context pluginContext) {
+        mDozePlugin = plugin;
+        mDozePlugin.setDozeRequester(this);
+    }
+
+    @Override
+    public void onPluginDisconnected(DozeServicePlugin plugin) {
+        if (mDozePlugin != null) {
+            mDozePlugin.onDreamingStopped();
+            mDozePlugin = null;
+        }
+    }
+
+    @Override
     public void onDreamingStarted() {
         super.onDreamingStarted();
         mDozeMachine.requestState(DozeMachine.State.INITIALIZED);
         startDozing();
         setDozeScreenBrightness(getResources().getInteger(
                 com.android.internal.R.integer.config_screenBrightnessDoze));
+        if (mDozePlugin != null) {
+            mDozePlugin.onDreamingStarted();
+        }
     }
 
     @Override
     public void onDreamingStopped() {
         super.onDreamingStopped();
         mDozeMachine.requestState(DozeMachine.State.FINISH);
+        if (mDozePlugin != null) {
+            mDozePlugin.onDreamingStopped();
+        }
     }
 
     @Override
@@ -79,4 +104,18 @@
         PowerManager pm = getSystemService(PowerManager.class);
         pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:NODOZE");
     }
+
+    @Override
+    public void onRequestShowDoze() {
+        if (mDozeMachine != null) {
+            mDozeMachine.requestState(DozeMachine.State.DOZE_AOD);
+        }
+    }
+
+    @Override
+    public void onRequestHideDoze() {
+        if (mDozeMachine != null) {
+            mDozeMachine.requestState(DozeMachine.State.DOZE);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 693a690..ae936db 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -98,12 +98,14 @@
         requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */);
     }
 
-    private void onSensor(int pulseReason, boolean sensorPerformedProxCheck) {
+    private void onSensor(int pulseReason, boolean sensorPerformedProxCheck,
+            float screenX, float screenY) {
         boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
         boolean isPickup = pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP;
 
         if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
             if (isDoubleTap) {
+                mDozeHost.onDoubleTap(screenX, screenY);
                 mMachine.wakeUp();
             } else {
                 mDozeHost.extendPulse();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index e8dcf6c..80a6418 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -25,6 +25,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.HardwareUiLayout;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.volume.VolumeDialogImpl;
@@ -1227,7 +1228,7 @@
             mClickListener = clickListener;
             mLongClickListener = longClickListener;
             mGradientDrawable = new GradientDrawable(mContext);
-            mColorExtractor = Dependency.get(ColorExtractor.class);
+            mColorExtractor = Dependency.get(SysuiColorExtractor.class);
 
             // Window initialization
             Window window = getWindow();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
index eaf715f..5b3ec08 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
@@ -35,9 +35,7 @@
     public CellTileView(Context context) {
         super(context);
         mSignalDrawable = new SignalDrawable(mContext);
-        float dark = Utils.getColorAttr(context, android.R.attr.colorForeground) == 0xff000000
-                ? 1 : 0;
-        mSignalDrawable.setDarkIntensity(dark);
+        mSignalDrawable.setDarkIntensity(isDark(mContext));
         mSignalDrawable.setIntrinsicSize(context.getResources().getDimensionPixelSize(
                 R.dimen.qs_tile_icon_size));
     }
@@ -50,6 +48,10 @@
         }
     }
 
+    private static int isDark(Context context) {
+        return Utils.getColorAttr(context, android.R.attr.colorForeground) == 0xff000000 ? 1 : 0;
+    }
+
     public static class SignalIcon extends Icon {
 
         private final int mState;
@@ -64,7 +66,11 @@
 
         @Override
         public Drawable getDrawable(Context context) {
-            return null;
+            //TODO: Not the optimal solution to create this drawable
+            SignalDrawable d = new SignalDrawable(context);
+            d.setDarkIntensity(isDark(context));
+            d.setLevel(getState());
+            return d;
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index f124e86..697db5f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -73,6 +73,7 @@
     private int mOpenY;
     private boolean mAnimatingOpen;
     private boolean mSwitchState;
+    private View mFooter;
 
     public QSDetail(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
@@ -116,9 +117,10 @@
         mDetailDoneButton.setOnClickListener(doneListener);
     }
 
-    public void setQsPanel(QSPanel panel, QuickStatusBarHeader header) {
+    public void setQsPanel(QSPanel panel, QuickStatusBarHeader header, View footer) {
         mQsPanel = panel;
         mHeader = header;
+        mFooter = footer;
         mHeader.setCallback(mQsPanelCallback);
         mQsPanel.setCallback(mQsPanelCallback);
     }
@@ -214,6 +216,7 @@
             mDetailAdapter = null;
             listener = mTeardownDetailWhenDone;
             mHeader.setVisibility(View.VISIBLE);
+            mFooter.setVisibility(View.VISIBLE);
             mQsPanel.setGridContentVisibility(true);
             mQsPanelCallback.onScanStateChanged(false);
         }
@@ -345,6 +348,7 @@
             if (mDetailAdapter != null) {
                 mQsPanel.setGridContentVisibility(false);
                 mHeader.setVisibility(View.INVISIBLE);
+                mFooter.setVisibility(View.INVISIBLE);
             }
             mAnimatingOpen = false;
             checkPendingAnimations();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 3f00276a..90275c5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -80,7 +80,7 @@
         mFooter = view.findViewById(R.id.qs_footer);
         mContainer = view.findViewById(id.quick_settings_container);
 
-        mQSDetail.setQsPanel(mQSPanel, mHeader);
+        mQSDetail.setQsPanel(mQSPanel, mHeader, mFooter);
 
         // If the quick settings row is not shown, then there is no need for the animation from
         // the row to the full QS panel.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index d434f2f..56fca1f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -27,6 +27,7 @@
 import android.text.method.LinkMovementMethod;
 import android.text.style.ClickableSpan;
 import android.util.Log;
+import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -244,8 +245,9 @@
 
         mDialog = new SystemUIDialog(mContext);
         mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
-        View dialogView = LayoutInflater.from(mContext)
-               .inflate(R.layout.quick_settings_footer_dialog, null, false);
+        View dialogView = LayoutInflater.from(
+                new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_Dialog))
+                .inflate(R.layout.quick_settings_footer_dialog, null, false);
         mDialog.setView(dialogView);
         mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java
index 603f66b..315a815 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java
@@ -18,12 +18,14 @@
 import android.graphics.drawable.Drawable;
 import android.widget.ImageView;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.plugins.qs.QSTile.SlashState;
 import com.android.systemui.qs.SlashDrawable;
 
 public class SlashImageView extends ImageView {
 
-    private SlashDrawable mSlash;
+    @VisibleForTesting
+    protected SlashDrawable mSlash;
 
     public SlashImageView(Context context) {
         super(context);
@@ -38,10 +40,13 @@
 
     @Override
     public void setImageDrawable(Drawable drawable) {
-        if (mSlash != null) {
-            mSlash.setDrawable(drawable);
-        } else {
+        if (drawable == null) {
+            mSlash = null;
+            super.setImageDrawable(null);
+        } else if (mSlash == null) {
             super.setImageDrawable(drawable);
+        } else {
+            mSlash.setDrawable(drawable);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 703be27..36677a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -183,12 +183,6 @@
 
         @Override
         public void onBluetoothDevicesChanged() {
-            mUiHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mDetailAdapter.updateItems();
-                }
-            });
             refreshState();
             if (isShowingDetail()) {
                 mDetailAdapter.updateItems();
@@ -202,6 +196,9 @@
     }
 
     protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
+        // We probably won't ever have space in the UI for more than 20 devices, so don't
+        // get info for them.
+        private static final int MAX_DEVICES = 20;
         private QSDetailItems mItems;
 
         @Override
@@ -264,13 +261,14 @@
             final Collection<CachedBluetoothDevice> devices = mController.getDevices();
             if (devices != null) {
                 int connectedDevices = 0;
+                int count = 0;
                 for (CachedBluetoothDevice device : devices) {
-                    if (device.getBondState() == BluetoothDevice.BOND_NONE) continue;
+                    if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
                     final Item item = new Item();
                     item.icon = R.drawable.ic_qs_bluetooth_on;
                     item.line1 = device.getName();
                     item.tag = device;
-                    int state = device.getMaxConnectionState();
+                    int state = mController.getMaxConnectionState(device);
                     if (state == BluetoothProfile.STATE_CONNECTED) {
                         item.icon = R.drawable.ic_qs_bluetooth_connected;
                         item.line2 = mContext.getString(R.string.quick_settings_connected);
@@ -284,6 +282,9 @@
                     } else {
                         items.add(item);
                     }
+                    if (++count == MAX_DEVICES) {
+                        break;
+                    }
                 }
             }
             mItems.setItems(items.toArray(new Item[items.size()]));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 319b463..d710244 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -46,6 +46,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
@@ -113,7 +114,7 @@
 
     private final float mScrimAlpha;
     private GradientDrawable mBackgroundScrim;
-    private final ColorExtractor mColorExtractor;
+    private final SysuiColorExtractor mColorExtractor;
     private Animator mBackgroundScrimAnimator;
 
     private RecentsTransitionHelper mTransitionHelper;
@@ -146,7 +147,7 @@
         mBackgroundScrim = new GradientDrawable(context);
         mBackgroundScrim.setCallback(this);
         mBackgroundScrim.setAlpha((int) (mScrimAlpha * 255));
-        mColorExtractor = Dependency.get(ColorExtractor.class);
+        mColorExtractor = Dependency.get(SysuiColorExtractor.class);
 
         LayoutInflater inflater = LayoutInflater.from(context);
         if (RecentsDebugFlags.Static.EnableStackActionButton) {
@@ -829,17 +830,23 @@
     }
 
     @Override
-    public void onColorsChanged(ColorExtractor extractor, int which) {
+    public void onColorsChanged(ColorExtractor colorExtractor, int which) {
         if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
-            mBackgroundScrim.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM));
+            // Recents doesn't care about the wallpaper being visible or not, it always
+            // wants to scrim with wallpaper colors
+            mBackgroundScrim.setColors(
+                    mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, true));
         }
     }
 
     public void onStart() {
         mColorExtractor.addOnColorsChangedListener(this);
+        // Getting system scrim colors ignoring wallpaper visibility since it should never be grey.
+        ColorExtractor.GradientColors systemColors = mColorExtractor.getColors(
+                WallpaperManager.FLAG_SYSTEM, true);
         // We don't want to interpolate colors because we're defining the initial state.
         // Gradient should be set/ready when you open "Recents".
-        mBackgroundScrim.setColors(mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM), false);
+        mBackgroundScrim.setColors(systemColors, false);
     }
 
     public void onStop() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index d7e7abe..6b7397b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -157,6 +157,10 @@
         return 2 * getPulseVisibleDuration();
     }
 
+    public boolean doubleTapReportsTouchCoordinates() {
+        return mContext.getResources().getBoolean(R.bool.doze_double_tap_reports_touch_coordinates);
+    }
+
 
     /**
      * Parses a spec of the form `1,2,3,!5,*`. The resulting object will match numbers that are
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index f502bb5..bc278e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -36,6 +36,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.ScrimView;
@@ -78,7 +79,7 @@
     private final View mHeadsUpScrim;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
-    private final ColorExtractor mColorExtractor;
+    private final SysuiColorExtractor mColorExtractor;
     private ColorExtractor.GradientColors mLockColors;
     private ColorExtractor.GradientColors mLockColorsDark;
     private ColorExtractor.GradientColors mSystemColors;
@@ -131,15 +132,17 @@
         mLightBarController = lightBarController;
         mScrimBehindAlpha = context.getResources().getFloat(R.dimen.scrim_behind_alpha);
 
-        mColorExtractor = Dependency.get(ColorExtractor.class);
+        mColorExtractor = Dependency.get(SysuiColorExtractor.class);
         mColorExtractor.addOnColorsChangedListener(this);
-        mLockColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK);
-        mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM);
+        mLockColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
+                ColorExtractor.TYPE_NORMAL, true /* ignoreVisibility */);
+        mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
+                ColorExtractor.TYPE_NORMAL, true /* ignoreVisibility */);
         // Darker gradient for the top scrim (mScrimInFront)
         mLockColorsDark = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
-                ColorExtractor.TYPE_DARK);
+                ColorExtractor.TYPE_DARK, true /* ignoreVisibility */);
         mSystemColorsDark = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
-                ColorExtractor.TYPE_DARK);
+                ColorExtractor.TYPE_DARK, true /* ignoreVisibility */);
         mNeedsDrawableColorUpdate = true;
 
         updateHeadsUpScrim(false);
@@ -659,18 +662,18 @@
     @Override
     public void onColorsChanged(ColorExtractor colorExtractor, int which) {
         if ((which & WallpaperManager.FLAG_LOCK) != 0) {
-            mLockColors = colorExtractor.getColors(WallpaperManager.FLAG_LOCK,
-                    ColorExtractor.TYPE_NORMAL);
-            mLockColorsDark = colorExtractor.getColors(WallpaperManager.FLAG_LOCK,
-                    ColorExtractor.TYPE_DARK);
+            mLockColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
+                    ColorExtractor.TYPE_NORMAL, true /* ignoreVisibility */);
+            mLockColorsDark = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
+                    ColorExtractor.TYPE_DARK, true /* ignoreVisibility */);
             mNeedsDrawableColorUpdate = true;
             scheduleUpdate();
         }
         if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
-            mSystemColors = colorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
-                    ColorExtractor.TYPE_NORMAL);
-            mSystemColorsDark = colorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
-                    ColorExtractor.TYPE_DARK);
+            mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
+                    ColorExtractor.TYPE_NORMAL, mKeyguardShowing);
+            mSystemColorsDark = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
+                    ColorExtractor.TYPE_DARK, mKeyguardShowing);
             mNeedsDrawableColorUpdate = true;
             scheduleUpdate();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index e8b6e07..c9d450d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -32,7 +32,6 @@
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
 
-import android.R.style;
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.annotation.NonNull;
@@ -169,6 +168,7 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.fragments.ExtensionFragmentListener;
@@ -303,7 +303,7 @@
     private static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
             = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
     public static final String TAG = "StatusBar";
-    public static final boolean DEBUG = true;
+    public static final boolean DEBUG = false;
     public static final boolean SPEW = false;
     public static final boolean DUMPTRUCK = true; // extra dumpsys info
     public static final boolean DEBUG_GESTURES = false;
@@ -734,7 +734,7 @@
     private HashMap<String, Entry> mPendingNotifications = new HashMap<>();
     private boolean mClearAllEnabled;
     @Nullable private View mAmbientIndicationContainer;
-    private ColorExtractor mColorExtractor;
+    private SysuiColorExtractor mColorExtractor;
     private ForegroundServiceController mForegroundServiceController;
 
     private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
@@ -781,7 +781,7 @@
         mOverlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
 
-        mColorExtractor = Dependency.get(ColorExtractor.class);
+        mColorExtractor = Dependency.get(SysuiColorExtractor.class);
         mColorExtractor.addOnColorsChangedListener(this);
 
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
@@ -4516,10 +4516,10 @@
         // Ignore visibility since we calculate the theme based on the real colors,
         // not the current state.
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
-            useDarkTheme = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK)
+            useDarkTheme = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK, true /* vis */)
                     .supportsDarkText();
         } else {
-            useDarkTheme = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM)
+            useDarkTheme = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, true /* vis */)
                     .supportsDarkText();
         }
 
@@ -5325,6 +5325,37 @@
             mAnimateWakeup = animateWakeup;
         }
 
+        @Override
+        public void onDoubleTap(float screenX, float screenY) {
+            if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null 
+                && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
+                mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2);
+                float viewX = screenX - mTmpInt2[0];
+                float viewY = screenY - mTmpInt2[1];
+                if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth()
+                        && 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) {
+                    dispatchDoubleTap(viewX, viewY);
+                }
+            }
+        }
+
+        public void dispatchDoubleTap(float viewX, float viewY) {
+            dispatchTap(mAmbientIndicationContainer, viewX, viewY);
+            dispatchTap(mAmbientIndicationContainer, viewX, viewY);
+        }
+
+        private void dispatchTap(View view, float x, float y) {
+            long now = SystemClock.elapsedRealtime();
+            dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_DOWN);
+            dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_UP);
+        }
+
+        private void dispatchTouchEvent(View view, float x, float y, long now, int action) {
+            MotionEvent ev = MotionEvent.obtain(now, now, action, x, y, 0 /* meta */);
+            view.dispatchTouchEvent(ev);
+            ev.recycle();
+        }
+
         private boolean shouldAnimateWakeup() {
             return mAnimateWakeup;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index df30e20..9daa199 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -37,6 +37,9 @@
     void disconnect(CachedBluetoothDevice device);
     boolean canConfigBluetooth();
 
+    int getMaxConnectionState(CachedBluetoothDevice device);
+    int getBondState(CachedBluetoothDevice device);
+
     public interface Callback {
         void onBluetoothStateChange(boolean enabled);
         void onBluetoothDevicesChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 36d24b3..dc4b115 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -18,6 +18,8 @@
 
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
@@ -33,8 +35,10 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.WeakHashMap;
 
 public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
         CachedBluetoothDevice.Callback {
@@ -44,18 +48,22 @@
     private final LocalBluetoothManager mLocalBluetoothManager;
     private final UserManager mUserManager;
     private final int mCurrentUser;
+    private final WeakHashMap<CachedBluetoothDevice, ActuallyCachedState> mCachedState =
+            new WeakHashMap<>();
+    private final Handler mBgHandler;
 
     private boolean mEnabled;
     private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
     private CachedBluetoothDevice mLastDevice;
 
-    private final H mHandler = new H();
+    private final H mHandler = new H(Looper.getMainLooper());
     private int mState;
 
     public BluetoothControllerImpl(Context context, Looper bgLooper) {
         mLocalBluetoothManager = Dependency.get(LocalBluetoothManager.class);
+        mBgHandler = new Handler(bgLooper);
         if (mLocalBluetoothManager != null) {
-            mLocalBluetoothManager.getEventManager().setReceiverHandler(new Handler(bgLooper));
+            mLocalBluetoothManager.getEventManager().setReceiverHandler(mBgHandler);
             mLocalBluetoothManager.getEventManager().registerCallback(this);
             onBluetoothStateChanged(
                     mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
@@ -106,6 +114,16 @@
     }
 
     @Override
+    public int getBondState(CachedBluetoothDevice device) {
+        return getCachedState(device).mBondState;
+    }
+
+    @Override
+    public int getMaxConnectionState(CachedBluetoothDevice device) {
+        return getCachedState(device).mMaxConnectionState;
+    }
+
+    @Override
     public void addCallback(Callback cb) {
         mHandler.obtainMessage(H.MSG_ADD_CALLBACK, cb).sendToTarget();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
@@ -225,12 +243,14 @@
 
     @Override
     public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+        mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
     }
 
     @Override
     public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+        mCachedState.remove(cachedDevice);
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
     }
@@ -243,11 +263,44 @@
 
     @Override
     public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+        mCachedState.remove(cachedDevice);
         mLastDevice = cachedDevice;
         updateConnected();
         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
     }
 
+    private ActuallyCachedState getCachedState(CachedBluetoothDevice device) {
+        ActuallyCachedState state = mCachedState.get(device);
+        if (state == null) {
+            state = new ActuallyCachedState(device, mHandler);
+            mBgHandler.post(state);
+            mCachedState.put(device, state);
+            return state;
+        }
+        return state;
+    }
+
+    private static class ActuallyCachedState implements Runnable {
+
+        private final WeakReference<CachedBluetoothDevice> mDevice;
+        private final Handler mUiHandler;
+        private int mBondState = BluetoothDevice.BOND_NONE;
+        private int mMaxConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+
+        private ActuallyCachedState(CachedBluetoothDevice device, Handler uiHandler) {
+            mDevice = new WeakReference<>(device);
+            mUiHandler = uiHandler;
+        }
+
+        @Override
+        public void run() {
+            mBondState = mDevice.get().getBondState();
+            mMaxConnectionState = mDevice.get().getMaxConnectionState();
+            mUiHandler.removeMessages(H.MSG_PAIRED_DEVICES_CHANGED);
+            mUiHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
+        }
+    }
+
     private final class H extends Handler {
         private final ArrayList<BluetoothController.Callback> mCallbacks = new ArrayList<>();
 
@@ -256,6 +309,10 @@
         private static final int MSG_ADD_CALLBACK = 3;
         private static final int MSG_REMOVE_CALLBACK = 4;
 
+        public H(Looper looper) {
+            super(looper);
+        }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 1d3b533..e11b23e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -18,7 +18,6 @@
 
 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.animation.ObjectAnimator;
@@ -62,7 +61,6 @@
 import android.view.View.OnClickListener;
 import android.view.View.OnTouchListener;
 import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.Window;
 import android.view.WindowManager;
@@ -77,7 +75,6 @@
 
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
-import com.android.systemui.HardwareUiLayout;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.plugins.VolumeDialogController;
@@ -103,8 +100,7 @@
  *
  * Methods ending in "H" must be called on the (ui) handler.
  */
-public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable,
-        ColorExtractor.OnColorsChangedListener {
+public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
     private static final String TAG = Util.logTag(VolumeDialogImpl.class);
 
     public static final String SHOW_FULL_ZEN = "sysui_show_full_zen";
@@ -114,8 +110,6 @@
 
     private final Context mContext;
     private final H mHandler = new H();
-    private final GradientDrawable mGradientDrawable;
-    private final ColorExtractor mColorExtractor;
     private VolumeDialogController mController;
 
     private Window mWindow;
@@ -170,9 +164,6 @@
                 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
         mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
         mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive);
-        mGradientDrawable = new GradientDrawable(mContext);
-        mGradientDrawable.setAlpha((int) (ScrimController.GRADIENT_SCRIM_ALPHA * 255));
-        mColorExtractor = Dependency.get(ColorExtractor.class);
     }
 
     public void init(int windowType, Callback callback) {
@@ -194,7 +185,6 @@
     @Override
     public void destroy() {
         mController.removeCallback(mControllerCallbackH);
-        mColorExtractor.removeOnColorsChangedListener(this);
     }
 
     private void initDialog() {
@@ -205,7 +195,7 @@
         mShowing = false;
         mWindow = mDialog.getWindow();
         mWindow.requestFeature(Window.FEATURE_NO_TITLE);
-        mWindow.setBackgroundDrawable(mGradientDrawable);
+        mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
         mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
         mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
@@ -213,49 +203,55 @@
                 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
-        Point displaySize = new Point();
-        mContext.getDisplay().getRealSize(displaySize);
         mDialog.setCanceledOnTouchOutside(true);
         final Resources res = mContext.getResources();
+        final WindowManager.LayoutParams lp = mWindow.getAttributes();
+        lp.type = mWindowType;
+        lp.format = PixelFormat.TRANSLUCENT;
+        lp.setTitle(VolumeDialogImpl.class.getSimpleName());
+        lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+        lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top);
+        lp.gravity = Gravity.TOP;
+        lp.windowAnimations = -1;
+        mWindow.setAttributes(lp);
         mWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
 
-        mDialog.setContentView(R.layout.volume_dialog_wrapped);
-        mDialogView = mDialog.findViewById(R.id.volume_dialog);
-        mDialogView.setOnHoverListener((v, event) -> {
-            int action = event.getActionMasked();
-            mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
-                    || (action == MotionEvent.ACTION_HOVER_MOVE);
-            rescheduleTimeoutH();
-            return true;
+        mDialog.setContentView(R.layout.volume_dialog);
+        mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
+        mDialogView.setOnHoverListener(new View.OnHoverListener() {
+            @Override
+            public boolean onHover(View v, MotionEvent event) {
+                int action = event.getActionMasked();
+                mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
+                        || (action == MotionEvent.ACTION_HOVER_MOVE);
+                rescheduleTimeoutH();
+                return true;
+            }
         });
-
-        mColorExtractor.addOnColorsChangedListener(this);
-        mGradientDrawable.setScreenSize(displaySize.x, displaySize.y);
-        ColorExtractor.GradientColors colors = mColorExtractor.getColors(
-                mKeyguard.isKeyguardLocked() ? WallpaperManager.FLAG_LOCK
-                        : WallpaperManager.FLAG_SYSTEM);
-        mGradientDrawable.setColors(colors, false);
-
-        mDialogContentView = mDialog.findViewById(R.id.volume_dialog_content);
-        mDialogRowsView = mDialogContentView.findViewById(R.id.volume_dialog_rows);
+        mDialogContentView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog_content);
+        mDialogRowsView = (ViewGroup) mDialogContentView.findViewById(R.id.volume_dialog_rows);
         mExpanded = false;
-        mExpandButton = mDialogView.findViewById(R.id.volume_expand_button);
+        mExpandButton = (ImageButton) mDialogView.findViewById(R.id.volume_expand_button);
         mExpandButton.setOnClickListener(mClickExpand);
 
         mExpandButton.setVisibility(
                 AudioSystem.isSingleVolume(mContext) ? View.GONE : View.VISIBLE);
+        updateWindowWidthH();
         updateExpandButtonH();
 
-        mMotion = new VolumeDialogMotion(mDialog, (View) mDialogView.getParent(),
-                mDialogContentView, mExpandButton, mGradientDrawable, animating -> {
-                    if (animating) return;
-                    if (mPendingStateChanged) {
-                        mHandler.sendEmptyMessage(H.STATE_CHANGED);
-                        mPendingStateChanged = false;
-                    }
-                    if (mPendingRecheckAll) {
-                        mHandler.sendEmptyMessage(H.RECHECK_ALL);
-                        mPendingRecheckAll = false;
+        mMotion = new VolumeDialogMotion(mDialog, mDialogView, mDialogContentView, mExpandButton,
+                new VolumeDialogMotion.Callback() {
+                    @Override
+                    public void onAnimatingChanged(boolean animating) {
+                        if (animating) return;
+                        if (mPendingStateChanged) {
+                            mHandler.sendEmptyMessage(H.STATE_CHANGED);
+                            mPendingStateChanged = false;
+                        }
+                        if (mPendingRecheckAll) {
+                            mHandler.sendEmptyMessage(H.RECHECK_ALL);
+                            mPendingRecheckAll = false;
+                        }
                     }
                 });
 
@@ -280,20 +276,11 @@
             addExistingRows();
         }
         mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
-        mZenFooter = mDialog.findViewById(R.id.volume_zen_footer);
+        mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
         mZenFooter.init(mZenModeController);
-        mZenPanel = mDialog.findViewById(R.id.tuner_zen_mode_panel);
+        mZenPanel = (TunerZenModePanel) mDialog.findViewById(R.id.tuner_zen_mode_panel);
         mZenPanel.init(mZenModeController);
         mZenPanel.setCallback(mZenPanelCallback);
-
-        final WindowManager.LayoutParams lp = mWindow.getAttributes();
-        lp.width = MATCH_PARENT;
-        lp.height = MATCH_PARENT;
-        lp.type = mWindowType;
-        lp.format = PixelFormat.TRANSLUCENT;
-        lp.setTitle(VolumeDialogImpl.class.getSimpleName());
-        lp.windowAnimations = -1;
-        mWindow.setAttributes(lp);
     }
 
     @Override
@@ -307,6 +294,20 @@
         return ColorStateList.valueOf(mContext.getColor(colorResId));
     }
 
+    private void updateWindowWidthH() {
+        final ViewGroup.LayoutParams lp = mDialogView.getLayoutParams();
+        final DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
+        if (D.BUG) Log.d(TAG, "updateWindowWidth dm.w=" + dm.widthPixels);
+        int w = dm.widthPixels;
+        final int max = mContext.getResources()
+                .getDimensionPixelSize(R.dimen.volume_dialog_panel_width);
+        if (w > max) {
+            w = max;
+        }
+        lp.width = w;
+        mDialogView.setLayoutParams(lp);
+    }
+
     public void setStreamImportant(int stream, boolean important) {
         mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
     }
@@ -547,8 +548,10 @@
     }
 
     private void updateDialogBottomMarginH() {
+        final long diff = System.currentTimeMillis() - mCollapseTime;
+        final boolean collapsing = mCollapseTime != 0 && diff < getConservativeCollapseDuration();
         final ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams();
-        final int bottomMargin =
+        final int bottomMargin = collapsing ? mDialogContentView.getHeight() :
                 mContext.getResources().getDimensionPixelSize(R.dimen.volume_dialog_margin_bottom);
         if (bottomMargin != mlp.bottomMargin) {
             if (D.BUG) Log.d(TAG, "bottomMargin " + mlp.bottomMargin + " -> " + bottomMargin);
@@ -578,7 +581,7 @@
         TransitionManager.endTransitions(mDialogView);
         final VolumeRow activeRow = getActiveRow();
         if (!dismissing) {
-            mWindow.setLayout(mWindow.getAttributes().width, MATCH_PARENT);
+            mWindow.setLayout(mWindow.getAttributes().width, ViewGroup.LayoutParams.MATCH_PARENT);
             TransitionManager.beginDelayedTransition(mDialogView, getTransistion());
         }
         updateRowsH(activeRow);
@@ -640,7 +643,7 @@
             final boolean isActive = row == activeRow;
             final boolean shouldBeVisible = shouldBeVisibleH(row, isActive);
             Util.setVisOrGone(row.view, shouldBeVisible);
-            Util.setVisOrGone(row.header, shouldBeVisible && mExpanded);
+            Util.setVisOrGone(row.header, shouldBeVisible);
             if (row.view.isShown()) {
                 updateVolumeRowSliderTintH(row, isActive);
             }
@@ -697,18 +700,12 @@
         final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF
                 && (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded)
                 && !mZenPanel.isEditing();
-
-        if (wasVisible != visible) {
-            mZenFooter.update();
-            if (visible) {
-                HardwareUiLayout.get(mZenFooter).setDivisionView(mZenFooter);
-            } else {
-                mHandler.postDelayed(() ->
-                                HardwareUiLayout.get(mZenFooter).setDivisionView(mZenFooter),
-                        mExpandButtonAnimationDuration);
-            }
-            Util.setVisOrGone(mZenFooter, visible);
+        TransitionManager.beginDelayedTransition(mDialogView, getTransistion());
+        if (wasVisible != visible && !visible) {
+            prepareForCollapse();
         }
+        Util.setVisOrGone(mZenFooter, visible);
+        mZenFooter.update();
 
         final boolean fullWasVisible = mZenPanel.getVisibility() == View.VISIBLE;
         final boolean fullVisible = mShowFullZen && !visible;
@@ -968,7 +965,8 @@
 
             @Override
             public void onTransitionEnd(Transition transition) {
-                mWindow.setLayout(MATCH_PARENT, MATCH_PARENT);
+                mWindow.setLayout(
+                        mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
             }
 
             @Override
@@ -977,7 +975,8 @@
 
             @Override
             public void onTransitionPause(Transition transition) {
-                mWindow.setLayout(MATCH_PARENT, MATCH_PARENT);
+                mWindow.setLayout(
+                        mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
             }
 
             @Override
@@ -1029,6 +1028,7 @@
                 initDialog();
                 mDensity = density;
             }
+            updateWindowWidthH();
             mConfigurableTexts.update();
             mZenFooter.onConfigurationChanged();
         }
@@ -1084,26 +1084,10 @@
             if (mExpandButtonAnimationRunning) return;
             final boolean newExpand = !mExpanded;
             Events.writeEvent(mContext, Events.EVENT_EXPAND, newExpand);
-            if (!newExpand) {
-                HardwareUiLayout.get(mDialogContentView).setCollapse();
-            }
             updateExpandedH(newExpand, false /* dismissing */);
         }
     };
 
-    @Override
-    public void onColorsChanged(ColorExtractor extractor, int which) {
-        if (mKeyguard.isKeyguardLocked()) {
-            if ((WallpaperManager.FLAG_LOCK & which) != 0) {
-                mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_LOCK));
-            }
-        } else {
-            if ((WallpaperManager.FLAG_SYSTEM & which) != 0) {
-                mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM));
-            }
-        }
-    }
-
     private final class H extends Handler {
         private static final int SHOW = 1;
         private static final int DISMISS = 2;
@@ -1175,8 +1159,8 @@
             event.setPackageName(mContext.getPackageName());
 
             ViewGroup.LayoutParams params = getWindow().getAttributes();
-            boolean isFullScreen = (params.width == MATCH_PARENT) &&
-                    (params.height == MATCH_PARENT);
+            boolean isFullScreen = (params.width == ViewGroup.LayoutParams.MATCH_PARENT) &&
+                    (params.height == ViewGroup.LayoutParams.MATCH_PARENT);
             event.setFullScreen(isFullScreen);
 
             if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
index 2df2227..01d31e2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.systemui.volume;
 
 import android.animation.Animator;
@@ -41,10 +42,8 @@
     private final View mDialogView;
     private final ViewGroup mContents;  // volume rows + zen footer
     private final View mChevron;
-    private final Drawable mBackground;
     private final Handler mHandler = new Handler();
     private final Callback mCallback;
-    private final int mBackgroundTargetAlpha;
 
     private boolean mAnimating;  // show or dismiss animation is running
     private boolean mShowing;  // show animation is running
@@ -53,14 +52,12 @@
     private ValueAnimator mContentsPositionAnimator;
 
     public VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron,
-            Drawable background, Callback callback) {
+            Callback callback) {
         mDialog = dialog;
         mDialogView = dialogView;
         mContents = contents;
         mChevron = chevron;
         mCallback = callback;
-        mBackground = background;
-        mBackgroundTargetAlpha = mBackground.getAlpha();
         mDialog.setOnDismissListener(new OnDismissListener() {
             @Override
             public void onDismiss(DialogInterface dialog) {
@@ -71,9 +68,8 @@
             @Override
             public void onShow(DialogInterface dialog) {
                 if (D.BUG) Log.d(TAG, "mDialog.onShow");
-                final int w = mDialogView.getWidth() / 4;
-                mDialogView.setTranslationX(w);
-                mBackground.setAlpha(0);
+                final int h = mDialogView.getHeight();
+                mDialogView.setTranslationY(-h);
                 startShowAnimation();
             }
         });
@@ -122,7 +118,7 @@
     }
 
     private int chevronDistance() {
-        return 0;
+        return mChevron.getHeight() / 6;
     }
 
     private int chevronPosY() {
@@ -133,29 +129,26 @@
     private void startShowAnimation() {
         if (D.BUG) Log.d(TAG, "startShowAnimation");
         mDialogView.animate()
-                .translationX(0)
                 .translationY(0)
-                .alpha(1)
                 .setDuration(scaledDuration(300))
                 .setInterpolator(new LogDecelerateInterpolator())
                 .setListener(null)
                 .setUpdateListener(animation -> {
-                    mBackground.setAlpha(
-                            (int) (animation.getAnimatedFraction() * mBackgroundTargetAlpha));
                     if (mChevronPositionAnimator != null) {
                         final float v = (Float) mChevronPositionAnimator.getAnimatedValue();
                         if (mChevronPositionAnimator == null) return;
                         // reposition chevron
                         final int posY = chevronPosY();
+                        mChevron.setTranslationY(posY + v + -mDialogView.getTranslationY());
                     }
                 })
                 .withEndAction(new Runnable() {
                     @Override
                     public void run() {
-                        mBackground.setAlpha(mBackgroundTargetAlpha);
                         if (mChevronPositionAnimator == null) return;
                         // reposition chevron
                         final int posY = chevronPosY();
+                        mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
                     }
                 })
                 .start();
@@ -171,13 +164,19 @@
                 if (D.BUG) Log.d(TAG, "show.onAnimationEnd");
                 setShowing(false);
             }
-
             @Override
             public void onAnimationCancel(Animator animation) {
                 if (D.BUG) Log.d(TAG, "show.onAnimationCancel");
                 mCancelled = true;
             }
         });
+        mContentsPositionAnimator.addUpdateListener(new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float v = (Float) animation.getAnimatedValue();
+                mContents.setTranslationY(v + -mDialogView.getTranslationY());
+            }
+        });
         mContentsPositionAnimator.setInterpolator(new LogDecelerateInterpolator());
         mContentsPositionAnimator.start();
 
@@ -219,30 +218,34 @@
             setShowing(false);
         }
         mDialogView.animate()
-                .translationX(mDialogView.getWidth() / 4)
-                .alpha(0)
+                .translationY(-mDialogView.getHeight())
                 .setDuration(scaledDuration(250))
                 .setInterpolator(new LogAccelerateInterpolator())
-                .setUpdateListener(animation -> {
-                    final float v = 1 - mChevronPositionAnimator.getAnimatedFraction();
-                    mBackground.setAlpha((int) (v * mBackgroundTargetAlpha));
+                .setUpdateListener(new AnimatorUpdateListener() {
+                    @Override
+                    public void onAnimationUpdate(ValueAnimator animation) {
+                        mContents.setTranslationY(-mDialogView.getTranslationY());
+                        final int posY = chevronPosY();
+                        mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
+                    }
                 })
                 .setListener(new AnimatorListenerAdapter() {
                     private boolean mCancelled;
-
                     @Override
                     public void onAnimationEnd(Animator animation) {
                         if (mCancelled) return;
                         if (D.BUG) Log.d(TAG, "dismiss.onAnimationEnd");
-                        mHandler.postDelayed(() -> {
-                            if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
-                            mDialog.dismiss();
-                            onComplete.run();
-                            setDismissing(false);
+                        mHandler.postDelayed(new Runnable() {
+                            @Override
+                            public void run() {
+                                if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
+                                mDialog.dismiss();
+                                onComplete.run();
+                                setDismissing(false);
+                            }
                         }, PRE_DISMISS_DELAY);
 
                     }
-
                     @Override
                     public void onAnimationCancel(Animator animation) {
                         if (D.BUG) Log.d(TAG, "dismiss.onAnimationCancel");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
new file mode 100644
index 0000000..7ed1e2c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
@@ -0,0 +1,104 @@
+/*
+ * 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.colorextraction;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.WallpaperColors;
+import android.app.WallpaperManager;
+import android.graphics.Color;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Pair;
+
+import com.android.systemui.SysuiTestCase;
+
+import com.google.android.colorextraction.ColorExtractor;
+import com.google.android.colorextraction.types.Tonal;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests color extraction generation.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SysuiColorExtractorTests extends SysuiTestCase {
+
+    private static int[] sWhich = new int[]{
+            WallpaperManager.FLAG_SYSTEM,
+            WallpaperManager.FLAG_LOCK};
+    private static int[] sTypes = new int[]{
+            ColorExtractor.TYPE_NORMAL,
+            ColorExtractor.TYPE_DARK,
+            ColorExtractor.TYPE_EXTRA_DARK};
+
+    @Test
+    public void getColors_usesGreyIfWallpaperNotVisible() {
+        ColorExtractor.GradientColors fallbackColors = new ColorExtractor.GradientColors();
+        fallbackColors.setMainColor(ColorExtractor.FALLBACK_COLOR);
+        fallbackColors.setSecondaryColor(ColorExtractor.FALLBACK_COLOR);
+
+        SysuiColorExtractor extractor = new SysuiColorExtractor(getContext(), new Tonal(), false);
+        simulateEvent(extractor);
+        extractor.setWallpaperVisible(false);
+
+        for (int which : sWhich) {
+            for (int type : sTypes) {
+                assertEquals("Not using fallback!", extractor.getColors(which, type),
+                        fallbackColors);
+            }
+        }
+    }
+
+    @Test
+    public void getColors_doesntUseFallbackIfVisible() {
+        ColorExtractor.GradientColors colors = new ColorExtractor.GradientColors();
+        colors.setMainColor(Color.RED);
+        colors.setSecondaryColor(Color.RED);
+
+        SysuiColorExtractor extractor = new SysuiColorExtractor(getContext(),
+                (inWallpaperColors, outGradientColorsNormal, outGradientColorsDark,
+                        outGradientColorsExtraDark) -> {
+                    outGradientColorsNormal.set(colors);
+                    outGradientColorsDark.set(colors);
+                    outGradientColorsExtraDark.set(colors);
+                    return true;
+                }, false);
+        simulateEvent(extractor);
+        extractor.setWallpaperVisible(true);
+
+        for (int which : sWhich) {
+            for (int type : sTypes) {
+                assertEquals("Not using extracted colors!",
+                        extractor.getColors(which, type), colors);
+            }
+        }
+    }
+
+    private void simulateEvent(SysuiColorExtractor extractor) {
+        // Let's fake a color event
+        List<Pair<Color, Integer>> dummyColors = new ArrayList<>();
+        dummyColors.add(new Pair<>(Color.valueOf(Color.BLACK), 1));
+        extractor.onColorsChanged(new WallpaperColors(dummyColors),
+                WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK);
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
index 56c07f9..2345110 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
@@ -28,6 +28,8 @@
     boolean pulseExtended;
     boolean animateWakeup;
     boolean dozing;
+    float doubleTapX;
+    float doubleTapY;
 
     @Override
     public void addCallback(@NonNull Callback callback) {
@@ -88,4 +90,10 @@
     public void setAnimateWakeup(boolean animateWakeup) {
         this.animateWakeup = animateWakeup;
     }
+
+    @Override
+    public void onDoubleTap(float x, float y) {
+        doubleTapX = y;
+        doubleTapY = y;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
index 59eca50..6a85511 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -23,6 +23,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import org.junit.After;
+import org.junit.Ignore;
 import android.support.test.filters.SmallTest;
 import android.support.test.filters.FlakyTest;
 import android.testing.AndroidTestingRunner;
@@ -38,8 +40,6 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.DetailAdapter;
 
-import org.junit.After;
-import org.junit.Ignore;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -67,7 +67,7 @@
             mQsDetail = (QSDetail) LayoutInflater.from(mContext).inflate(R.layout.qs_detail, null);
             mQsPanel = mock(QSPanel.class);
             mQuickHeader = mock(QuickStatusBarHeader.class);
-            mQsDetail.setQsPanel(mQsPanel, mQuickHeader);
+            mQsDetail.setQsPanel(mQsPanel, mQuickHeader, mock(View.class));
 
             mMockDetailAdapter = mock(DetailAdapter.class);
             when(mMockDetailAdapter.createDetailView(any(), any(), any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/SlashImageViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/SlashImageViewTest.java
new file mode 100644
index 0000000..aef584f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/SlashImageViewTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.qs;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.qs.QSTile.SlashState;
+import com.android.systemui.qs.tileimpl.SlashImageView;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class SlashImageViewTest extends SysuiTestCase {
+    private TestableSlashImageView mSlashView;
+
+    @Test
+    public void testSetSlashStateCreatesSlashDrawable() {
+        SlashState mockState = mock(SlashState.class);
+        Drawable mockDrawable = mock(Drawable.class);
+        mSlashView = new TestableSlashImageView(mContext);
+        assertTrue(mSlashView.getSlashDrawable() == null);
+
+        mSlashView.setImageDrawable(mockDrawable);
+        mSlashView.setState(mockState);
+
+        assertTrue(mSlashView.getSlashDrawable() != null);
+    }
+
+    @Test
+    public void testSetNullDrawableRemovesSlashDrawable() {
+        SlashState mockState = mock(SlashState.class);
+        Drawable mockDrawable = mock(Drawable.class);
+
+        mSlashView = new TestableSlashImageView(mContext);
+        mSlashView.setImageDrawable(mockDrawable);
+        mSlashView.setState(mockState);
+        mSlashView.setImageDrawable(null);
+
+        assertTrue(mSlashView.getSlashDrawable() == null);
+    }
+
+    // Expose getSlashDrawable
+    private static class TestableSlashImageView extends SlashImageView {
+        TestableSlashImageView(Context c) {
+            super(c);
+        }
+
+        private SlashDrawable getSlashDrawable() {
+            return mSlash;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 2eb9560..4cc8bca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -14,16 +14,21 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.os.Looper;
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
+import android.util.Log;
 
 import com.android.settingslib.bluetooth.BluetoothEventManager;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -80,4 +85,56 @@
                 BluetoothAdapter.STATE_DISCONNECTED);
         assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
     }
+
+    @Test
+    public void testDefaultConnectionState() {
+        CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+        assertEquals(BluetoothDevice.BOND_NONE, mBluetoothControllerImpl.getBondState(device));
+        assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mBluetoothControllerImpl.getMaxConnectionState(device));
+    }
+
+    @Test
+    public void testAsyncBondState() throws Exception {
+        CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+        when(device.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
+        mBluetoothControllerImpl.addCallback(callback);
+
+        // Grab the main looper, we'll need it later.
+        TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
+
+        // Trigger the state getting.
+        assertEquals(BluetoothDevice.BOND_NONE, mBluetoothControllerImpl.getBondState(device));
+
+        mTestableLooper.processMessages(1);
+        mainLooper.processAllMessages();
+
+        assertEquals(BluetoothDevice.BOND_BONDED, mBluetoothControllerImpl.getBondState(device));
+        verify(callback).onBluetoothDevicesChanged();
+        mainLooper.destroy();
+    }
+
+    @Test
+    public void testAsyncConnectionState() throws Exception {
+        CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+        when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
+        BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
+        mBluetoothControllerImpl.addCallback(callback);
+
+        // Grab the main looper, we'll need it later.
+        TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
+
+        // Trigger the state getting.
+        assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mBluetoothControllerImpl.getMaxConnectionState(device));
+
+        mTestableLooper.processMessages(1);
+        mainLooper.processAllMessages();
+
+        assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mBluetoothControllerImpl.getMaxConnectionState(device));
+        verify(callback).onBluetoothDevicesChanged();
+        mainLooper.destroy();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
index 0ba0319..9ec096a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
@@ -83,4 +83,14 @@
     public boolean canConfigBluetooth() {
         return false;
     }
+
+    @Override
+    public int getMaxConnectionState(CachedBluetoothDevice device) {
+        return 0;
+    }
+
+    @Override
+    public int getBondState(CachedBluetoothDevice device) {
+        return 0;
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 76456f3..ad442c7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1804,7 +1804,7 @@
                     }
                     AppErrorResult res = (AppErrorResult) data.get("result");
                     if (mShowDialogs && !mSleeping && !mShuttingDown) {
-                        Dialog d = new StrictModeViolationDialog(mContext,
+                        Dialog d = new StrictModeViolationDialog(mUiContext,
                                 ActivityManagerService.this, res, proc);
                         d.show();
                         proc.crashDialog = d;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index ba8fa31..f720cd5 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -600,6 +600,7 @@
     void finishUserStopping(final int userId, final UserState uss) {
         // On to the next.
         final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
+        shutdownIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         // This is the result receiver for the final shutdown broadcast.
         final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
             @Override
diff --git a/services/core/java/com/android/server/display/OverlayDisplayWindow.java b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
index f23caf2..0fdf2da 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayWindow.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
@@ -30,6 +30,7 @@
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
 import android.view.TextureView;
+import android.view.ThreadedRenderer;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.TextureView.SurfaceTextureListener;
@@ -95,6 +96,8 @@
     public OverlayDisplayWindow(Context context, String name,
             int width, int height, int densityDpi, int gravity, boolean secure,
             Listener listener) {
+        // Workaround device freeze (b/38372997)
+        ThreadedRenderer.disableVsync();
         mContext = context;
         mName = name;
         mGravity = gravity;
diff --git a/services/core/java/com/android/server/job/GrantedUriPermissions.java b/services/core/java/com/android/server/job/GrantedUriPermissions.java
index e413d8d..c23b109 100644
--- a/services/core/java/com/android/server/job/GrantedUriPermissions.java
+++ b/services/core/java/com/android/server/job/GrantedUriPermissions.java
@@ -29,7 +29,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
-public class GrantedUriPermissions {
+public final class GrantedUriPermissions {
     private final int mGrantFlags;
     private final int mSourceUserId;
     private final String mTag;
diff --git a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
index 2d2f61f..a53c088 100644
--- a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -26,7 +26,7 @@
 
 import java.io.PrintWriter;
 
-public class JobSchedulerShellCommand extends ShellCommand {
+public final class JobSchedulerShellCommand extends ShellCommand {
     public static final int CMD_ERR_NO_PACKAGE = -1000;
     public static final int CMD_ERR_NO_JOB = -1001;
     public static final int CMD_ERR_CONSTRAINTS = -1002;
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 637db11..5d3f6f7 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -55,13 +55,13 @@
  * job lands, and again when it is complete.
  * - Cancelling is trickier, because there are also interactions from the client. It's possible
  * the {@link com.android.server.job.JobServiceContext.JobServiceHandler} tries to process a
- * {@link #doCancelLocked(int)} after the client has already finished. This is handled by having
+ * {@link #doCancelLocked} after the client has already finished. This is handled by having
  * {@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelLocked} check whether
  * the context is still valid.
  * To mitigate this, we avoid sending duplicate onStopJob()
  * calls to the client after they've specified jobFinished().
  */
-public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection {
+public final class JobServiceContext implements ServiceConnection {
     private static final boolean DEBUG = JobSchedulerService.DEBUG;
     private static final String TAG = "JobServiceContext";
     /** Amount of time a job is allowed to execute for before being considered timed-out. */
@@ -112,6 +112,7 @@
      * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
      */
     private JobStatus mRunningJob;
+    private JobCallback mRunningCallback;
     /** Used to store next job to run when current job is to be preempted. */
     private int mPreferredUid;
     IJobService service;
@@ -133,6 +134,36 @@
     // Debugging: time this job was last stopped.
     public long mStoppedTime;
 
+    final class JobCallback extends IJobCallback.Stub {
+        public String mStoppedReason;
+        public long mStoppedTime;
+
+        @Override
+        public void acknowledgeStartMessage(int jobId, boolean ongoing) {
+            doAcknowledgeStartMessage(this, jobId, ongoing);
+        }
+
+        @Override
+        public void acknowledgeStopMessage(int jobId, boolean reschedule) {
+            doAcknowledgeStopMessage(this, jobId, reschedule);
+        }
+
+        @Override
+        public JobWorkItem dequeueWork(int jobId) {
+            return doDequeueWork(this, jobId);
+        }
+
+        @Override
+        public boolean completeWork(int jobId, int workId) {
+            return doCompleteWork(this, jobId, workId);
+        }
+
+        @Override
+        public void jobFinished(int jobId, boolean reschedule) {
+            doJobFinished(this, jobId, reschedule);
+        }
+    }
+
     JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
             JobPackageTracker tracker, Looper looper) {
         this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
@@ -168,6 +199,7 @@
             mPreferredUid = NO_PREFERRED_UID;
 
             mRunningJob = job;
+            mRunningCallback = new JobCallback();
             final boolean isDeadlineExpired =
                     job.hasDeadlineConstraint() &&
                             (job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime());
@@ -182,7 +214,7 @@
                 job.changedAuthorities.toArray(triggeredAuthorities);
             }
             final JobInfo ji = job.getJob();
-            mParams = new JobParameters(this, job.getJobId(), ji.getExtras(),
+            mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
                     ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
                     isDeadlineExpired, triggeredUris, triggeredAuthorities);
             mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
@@ -198,6 +230,7 @@
                     Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
                 }
                 mRunningJob = null;
+                mRunningCallback = null;
                 mParams = null;
                 mExecutionStartTimeElapsed = 0L;
                 mVerb = VERB_FINISHED;
@@ -263,28 +296,29 @@
         return false;
     }
 
-    @Override
-    public void jobFinished(int jobId, boolean reschedule) {
-        doCallback(reschedule, "app called jobFinished");
+    void doJobFinished(JobCallback cb, int jobId, boolean reschedule) {
+        doCallback(cb, reschedule, "app called jobFinished");
     }
 
-    @Override
-    public void acknowledgeStopMessage(int jobId, boolean reschedule) {
-        doCallback(reschedule, null);
+    void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) {
+        doCallback(cb, reschedule, null);
     }
 
-    @Override
-    public void acknowledgeStartMessage(int jobId, boolean ongoing) {
-        doCallback(ongoing, "finished start");
+    void doAcknowledgeStartMessage(JobCallback cb, int jobId, boolean ongoing) {
+        doCallback(cb, ongoing, "finished start");
     }
 
-    @Override
-    public JobWorkItem dequeueWork(int jobId) {
-        final int callingUid = Binder.getCallingUid();
+    JobWorkItem doDequeueWork(JobCallback cb, int jobId) {
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                assertCallingUidLocked(callingUid);
+                assertCallerLocked(cb);
+                if (mVerb == VERB_STOPPING || mVerb == VERB_FINISHED) {
+                    // This job is either all done, or on its way out.  Either way, it
+                    // should not dispatch any more work.  We will pick up any remaining
+                    // work the next time we start the job again.
+                    return null;
+                }
                 final JobWorkItem work = mRunningJob.dequeueWorkLocked();
                 if (work == null && !mRunningJob.hasExecutingWorkLocked()) {
                     // This will finish the job.
@@ -297,13 +331,11 @@
         }
     }
 
-    @Override
-    public boolean completeWork(int jobId, int workId) {
-        final int callingUid = Binder.getCallingUid();
+    boolean doCompleteWork(JobCallback cb, int jobId, int workId) {
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                assertCallingUidLocked(callingUid);
+                assertCallerLocked(cb);
                 return mRunningJob.completeWorkLocked(ActivityManager.getService(), workId);
             }
         } finally {
@@ -369,8 +401,8 @@
      * whether the client exercising the callback is the client we expect.
      * @return True if the binder calling is coming from the client we expect.
      */
-    private boolean verifyCallingUidLocked(final int callingUid) {
-        if (mRunningJob == null || callingUid != mRunningJob.getUid()) {
+    private boolean verifyCallerLocked(JobCallback cb) {
+        if (mRunningCallback != cb) {
             if (DEBUG) {
                 Slog.d(TAG, "Stale callback received, ignoring.");
             }
@@ -379,16 +411,15 @@
         return true;
     }
 
-    private void assertCallingUidLocked(final int callingUid) {
-        if (!verifyCallingUidLocked(callingUid)) {
+    private void assertCallerLocked(JobCallback cb) {
+        if (!verifyCallerLocked(cb)) {
             StringBuilder sb = new StringBuilder(128);
-            sb.append("Bad calling uid ");
-            sb.append(callingUid);
-            if (mStoppedReason != null) {
+            sb.append("Caller no longer running");
+            if (cb.mStoppedReason != null) {
                 sb.append(", last stopped ");
-                TimeUtils.formatDuration(SystemClock.elapsedRealtime() - mStoppedTime, sb);
+                TimeUtils.formatDuration(SystemClock.elapsedRealtime() - cb.mStoppedTime, sb);
                 sb.append(" because: ");
-                sb.append(mStoppedReason);
+                sb.append(cb.mStoppedReason);
             }
             throw new SecurityException(sb.toString());
         }
@@ -421,12 +452,11 @@
         handleServiceBoundLocked();
     }
 
-    void doCallback(boolean reschedule, String reason) {
-        final int callingUid = Binder.getCallingUid();
+    void doCallback(JobCallback cb, boolean reschedule, String reason) {
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                if (!verifyCallingUidLocked(callingUid)) {
+                if (!verifyCallerLocked(cb)) {
                     return;
                 }
                 doCallbackLocked(reschedule, reason);
@@ -559,7 +589,7 @@
      * VERB_BINDING    -> Cancelled before bind completed. Mark as cancelled and wait for
      *                    {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
      *     _STARTING   -> Mark as cancelled and wait for
-     *                    {@link JobServiceContext#acknowledgeStartMessage(int, boolean)}
+     *                    {@link JobServiceContext#doAcknowledgeStartMessage}
      *     _EXECUTING  -> call {@link #sendStopMessageLocked}}, but only if there are no callbacks
      *                      in the message queue.
      *     _ENDING     -> No point in doing anything here, so we ignore.
@@ -671,6 +701,7 @@
         mContext.unbindService(JobServiceContext.this);
         mWakeLock = null;
         mRunningJob = null;
+        mRunningCallback = null;
         mParams = null;
         mVerb = VERB_FINISHED;
         mCancelled = false;
@@ -684,6 +715,10 @@
         if (reason != null && mStoppedReason == null) {
             mStoppedReason = reason;
             mStoppedTime = SystemClock.elapsedRealtime();
+            if (mRunningCallback != null) {
+                mRunningCallback.mStoppedReason = mStoppedReason;
+                mRunningCallback.mStoppedTime = mStoppedTime;
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 22eed3b..f0cd8a8 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -66,7 +66,7 @@
  *      and {@link com.android.server.job.JobStore.ReadJobMapFromDiskRunnable} lock on that
  *      object.
  */
-public class JobStore {
+public final class JobStore {
     private static final String TAG = "JobStore";
     private static final boolean DEBUG = JobSchedulerService.DEBUG;
 
@@ -263,7 +263,7 @@
      * Runnable that writes {@link #mJobSet} out to xml.
      * NOTE: This Runnable locks on mLock
      */
-    private class WriteJobsMapToDiskRunnable implements Runnable {
+    private final class WriteJobsMapToDiskRunnable implements Runnable {
         @Override
         public void run() {
             final long startElapsed = SystemClock.elapsedRealtime();
@@ -444,7 +444,7 @@
      * Runnable that reads list of persisted job from xml. This is run once at start up, so doesn't
      * need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
      */
-    private class ReadJobMapFromDiskRunnable implements Runnable {
+    private final class ReadJobMapFromDiskRunnable implements Runnable {
         private final JobSet jobSet;
 
         /**
@@ -796,7 +796,7 @@
         }
     }
 
-    static class JobSet {
+    static final class JobSet {
         // Key is the getUid() originator of the jobs in each sheaf
         private SparseArray<ArraySet<JobStatus>> mJobs;
 
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index 68dd00f..39f2a96 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -33,7 +33,7 @@
  * for a certain amount of time (maybe hours or days) are considered idle. When the app comes
  * out of idle state, it will be allowed to run scheduled jobs.
  */
-public class AppIdleController extends StateController {
+public final class AppIdleController extends StateController {
 
     private static final String LOG_TAG = "AppIdleController";
     private static final boolean DEBUG = false;
@@ -171,7 +171,7 @@
         }
     }
 
-    private class AppIdleStateChangeListener
+    private final class AppIdleStateChangeListener
             extends UsageStatsManagerInternal.AppIdleStateChangeListener {
         @Override
         public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index d275bd9..9111969 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -39,7 +39,7 @@
  * be charging when it's been plugged in for more than two minutes, and the system has broadcast
  * ACTION_BATTERY_OK.
  */
-public class BatteryController extends StateController {
+public final class BatteryController extends StateController {
     private static final String TAG = "JobScheduler.Batt";
 
     private static final Object sCreationLock = new Object();
@@ -121,7 +121,7 @@
         }
     }
 
-    public class ChargingTracker extends BroadcastReceiver {
+    public final class ChargingTracker extends BroadcastReceiver {
         /**
          * Track whether we're "charging", where charging means that we're ready to commit to
          * doing work.
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index f426818..17c8928 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -43,7 +43,7 @@
  * status due to user-requested network policies, so we need to check
  * constraints on a per-UID basis.
  */
-public class ConnectivityController extends StateController implements
+public final class ConnectivityController extends StateController implements
         ConnectivityManager.OnNetworkActiveListener {
     private static final String TAG = "JobScheduler.Conn";
     private static final boolean DEBUG = false;
diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
index cfafc38..ff807ec 100644
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
@@ -39,7 +39,7 @@
 /**
  * Controller for monitoring changes to content URIs through a ContentObserver.
  */
-public class ContentObserverController extends StateController {
+public final class ContentObserverController extends StateController {
     private static final String TAG = "JobScheduler.Content";
     private static final boolean DEBUG = false;
 
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 5ccf812..85993b9 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -37,7 +37,7 @@
  * When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied.
  * When device is not dozing, set constraint for all jobs as satisfied.
  */
-public class DeviceIdleJobsController extends StateController {
+public final class DeviceIdleJobsController extends StateController {
 
     private static final String LOG_TAG = "DeviceIdleJobsController";
     private static final boolean LOG_DEBUG = false;
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index 7e92293..9eda046 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -33,7 +33,7 @@
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
 
-public class IdleController extends StateController {
+public final class IdleController extends StateController {
     private static final String TAG = "IdleController";
 
     // Policy: we decide that we're "idle" if the device has been unused /
@@ -107,7 +107,7 @@
         mIdleTracker.startTracking();
     }
 
-    class IdlenessTracker extends BroadcastReceiver {
+    final class IdlenessTracker extends BroadcastReceiver {
         private AlarmManager mAlarm;
         private PendingIntent mIdleTriggerIntent;
         boolean mIdle;
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 53bf402..446b0d6 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -698,7 +698,8 @@
     static final int CONSTRAINTS_OF_INTEREST =
             CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW |
             CONSTRAINT_TIMING_DELAY |
-            CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED | CONSTRAINT_NOT_ROAMING |
+            CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED |
+            CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED |
             CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
 
     // Soft override covers all non-"functional" constraints
@@ -865,6 +866,9 @@
         if ((constraints&CONSTRAINT_NOT_ROAMING) != 0) {
             pw.print(" NOT_ROAMING");
         }
+        if ((constraints&CONSTRAINT_METERED) != 0) {
+            pw.print(" METERED");
+        }
         if ((constraints&CONSTRAINT_APP_NOT_IDLE) != 0) {
             pw.print(" APP_NOT_IDLE");
         }
diff --git a/services/core/java/com/android/server/job/controllers/StorageController.java b/services/core/java/com/android/server/job/controllers/StorageController.java
index 4fe8eca..c24e563 100644
--- a/services/core/java/com/android/server/job/controllers/StorageController.java
+++ b/services/core/java/com/android/server/job/controllers/StorageController.java
@@ -35,7 +35,7 @@
 /**
  * Simple controller that tracks the status of the device's storage.
  */
-public class StorageController extends StateController {
+public final class StorageController extends StateController {
     private static final String TAG = "JobScheduler.Stor";
 
     private static final Object sCreationLock = new Object();
@@ -112,7 +112,7 @@
         }
     }
 
-    public class StorageTracker extends BroadcastReceiver {
+    public final class StorageTracker extends BroadcastReceiver {
         /**
          * Track whether storage is low.
          */
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index 01c841e..d90699a 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -38,7 +38,7 @@
  * This class sets an alarm for the next expiring job, and determines whether a job's minimum
  * delay has been satisfied.
  */
-public class TimeController extends StateController {
+public final class TimeController extends StateController {
     private static final String TAG = "JobScheduler.Time";
 
     /** Deadline alarm tag for logging purposes */
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9c04801..bf3e5d3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -49,6 +49,7 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
 import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
@@ -677,13 +678,6 @@
     @GuardedBy("mPackages")
     final SparseIntArray mIsolatedOwners = new SparseIntArray();
 
-    // List of APK paths to load for each user and package. This data is never
-    // persisted by the package manager. Instead, the overlay manager will
-    // ensure the data is up-to-date in runtime.
-    @GuardedBy("mPackages")
-    final SparseArray<ArrayMap<String, ArrayList<String>>> mEnabledOverlayPaths =
-        new SparseArray<ArrayMap<String, ArrayList<String>>>();
-
     /**
      * Tracks new system packages [received in an OTA] that we expect to
      * find updated user-installed versions. Keys are package name, values
@@ -3584,8 +3578,6 @@
             return null;
         }
 
-        rebaseEnabledOverlays(packageInfo.applicationInfo, userId);
-
         packageInfo.packageName = packageInfo.applicationInfo.packageName =
                 resolveExternalPackageNameLPr(p);
 
@@ -4097,7 +4089,6 @@
             ApplicationInfo ai = PackageParser.generateApplicationInfo(ps.pkg, flags,
                     ps.readUserState(userId), userId);
             if (ai != null) {
-                rebaseEnabledOverlays(ai, userId);
                 ai.packageName = resolveExternalPackageNameLPr(ps.pkg);
             }
             return ai;
@@ -4146,7 +4137,6 @@
                 ApplicationInfo ai = PackageParser.generateApplicationInfo(
                         p, flags, ps.readUserState(userId), userId);
                 if (ai != null) {
-                    rebaseEnabledOverlays(ai, userId);
                     ai.packageName = resolveExternalPackageNameLPr(p);
                 }
                 return ai;
@@ -4163,26 +4153,6 @@
         return null;
     }
 
-    private void rebaseEnabledOverlays(@NonNull ApplicationInfo ai, int userId) {
-        List<String> paths = new ArrayList<>();
-        ArrayMap<String, ArrayList<String>> userSpecificOverlays =
-            mEnabledOverlayPaths.get(userId);
-        if (userSpecificOverlays != null) {
-            if (!"android".equals(ai.packageName)) {
-                ArrayList<String> frameworkOverlays = userSpecificOverlays.get("android");
-                if (frameworkOverlays != null) {
-                    paths.addAll(frameworkOverlays);
-                }
-            }
-
-            ArrayList<String> appOverlays = userSpecificOverlays.get(ai.packageName);
-            if (appOverlays != null) {
-                paths.addAll(appOverlays);
-            }
-        }
-        ai.resourceDirs = paths.size() > 0 ? paths.toArray(new String[paths.size()]) : null;
-    }
-
     private String normalizePackageNameLPr(String packageName) {
         String normalizedPackageName = mSettings.getRenamedPackageLPr(packageName);
         return normalizedPackageName != null ? normalizedPackageName : packageName;
@@ -4564,24 +4534,6 @@
         return updateFlagsForComponent(flags, userId, intent /*cookie*/);
     }
 
-    private ActivityInfo generateActivityInfo(ActivityInfo ai, int flags, PackageUserState state,
-            int userId) {
-        ActivityInfo ret = PackageParser.generateActivityInfo(ai, flags, state, userId);
-        if (ret != null) {
-            rebaseEnabledOverlays(ret.applicationInfo, userId);
-        }
-        return ret;
-    }
-
-    private ActivityInfo generateActivityInfo(PackageParser.Activity a, int flags,
-            PackageUserState state, int userId) {
-        ActivityInfo ai = PackageParser.generateActivityInfo(a, flags, state, userId);
-        if (ai != null) {
-            rebaseEnabledOverlays(ai.applicationInfo, userId);
-        }
-        return ai;
-    }
-
     @Override
     public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
         return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId);
@@ -4609,11 +4561,12 @@
                 if (filterAppAccessLPr(ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) {
                     return null;
                 }
-                return generateActivityInfo(a, flags, ps.readUserState(userId), userId);
+                return PackageParser.generateActivityInfo(
+                        a, flags, ps.readUserState(userId), userId);
             }
             if (mResolveComponentName.equals(component)) {
-                return generateActivityInfo(mResolveActivity, flags, new PackageUserState(),
-                        userId);
+                return PackageParser.generateActivityInfo(
+                        mResolveActivity, flags, new PackageUserState(), userId);
             }
         }
         return null;
@@ -4667,7 +4620,8 @@
                 if (filterAppAccessLPr(ps, callingUid, component, TYPE_RECEIVER, userId)) {
                     return null;
                 }
-                return generateActivityInfo(a, flags, ps.readUserState(userId), userId);
+                return PackageParser.generateActivityInfo(
+                        a, flags, ps.readUserState(userId), userId);
             }
         }
         return null;
@@ -4803,12 +4757,8 @@
                 if (filterAppAccessLPr(ps, callingUid, component, TYPE_SERVICE, userId)) {
                     return null;
                 }
-                ServiceInfo si = PackageParser.generateServiceInfo(s, flags,
-                        ps.readUserState(userId), userId);
-                if (si != null) {
-                    rebaseEnabledOverlays(si.applicationInfo, userId);
-                }
-                return si;
+                return PackageParser.generateServiceInfo(
+                        s, flags, ps.readUserState(userId), userId);
             }
         }
         return null;
@@ -4831,12 +4781,8 @@
                 if (filterAppAccessLPr(ps, callingUid, component, TYPE_PROVIDER, userId)) {
                     return null;
                 }
-                ProviderInfo pi = PackageParser.generateProviderInfo(p, flags,
-                        ps.readUserState(userId), userId);
-                if (pi != null) {
-                    rebaseEnabledOverlays(pi.applicationInfo, userId);
-                }
-                return pi;
+                return PackageParser.generateProviderInfo(
+                        p, flags, ps.readUserState(userId), userId);
             }
         }
         return null;
@@ -8262,7 +8208,6 @@
                         ai = PackageParser.generateApplicationInfo(ps.pkg, effectiveFlags,
                                 ps.readUserState(userId), userId);
                         if (ai != null) {
-                            rebaseEnabledOverlays(ai, userId);
                             ai.packageName = resolveExternalPackageNameLPr(ps.pkg);
                         }
                     } else {
@@ -8289,7 +8234,6 @@
                         ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags,
                                 ps.readUserState(userId), userId);
                         if (ai != null) {
-                            rebaseEnabledOverlays(ai, userId);
                             ai.packageName = resolveExternalPackageNameLPr(p);
                             list.add(ai);
                         }
@@ -8443,7 +8387,6 @@
                         ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags,
                                 ps.readUserState(userId), userId);
                         if (ai != null) {
-                            rebaseEnabledOverlays(ai, userId);
                             finalList.add(ai);
                         }
                     }
@@ -13367,7 +13310,8 @@
                 return null;
             }
             final PackageUserState userState = ps.readUserState(userId);
-            ActivityInfo ai = generateActivityInfo(activity, mFlags, userState, userId);
+            ActivityInfo ai =
+                    PackageParser.generateActivityInfo(activity, mFlags, userState, userId);
             if (ai == null) {
                 return null;
             }
@@ -17896,16 +17840,17 @@
 
         // Instant apps must have target SDK >= O and have targetSanboxVersion >= 2
         if (instantApp && pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
-            Slog.w(TAG, "Instant app package " + pkg.packageName
-                    + " does not target O, this will be a fatal error.");
-            // STOPSHIP: Make this a fatal error
-            pkg.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
+            Slog.w(TAG, "Instant app package " + pkg.packageName + " does not target O");
+            res.setError(INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE,
+                    "Instant app package must target O");
+            return;
         }
         if (instantApp && pkg.applicationInfo.targetSandboxVersion != 2) {
             Slog.w(TAG, "Instant app package " + pkg.packageName
-                    + " does not target targetSandboxVersion 2, this will be a fatal error.");
-            // STOPSHIP: Make this a fatal error
-            pkg.applicationInfo.targetSandboxVersion = 2;
+                    + " does not target targetSandboxVersion 2");
+            res.setError(INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE,
+                    "Instant app package must use targetSanboxVersion 2");
+            return;
         }
 
         if (pkg.applicationInfo.isStaticSharedLibrary()) {
@@ -22346,11 +22291,6 @@
                 dumpCompilerStatsLPr(pw, packageName);
             }
 
-            if (!checkin && dumpState.isDumping(DumpState.DUMP_ENABLED_OVERLAYS)) {
-                if (dumpState.onTitlePrinted()) pw.println();
-                dumpEnabledOverlaysLPr(pw);
-            }
-
             if (!checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) {
                 if (dumpState.onTitlePrinted()) pw.println();
                 mSettings.dumpReadMessagesLPr(pw, dumpState);
@@ -22547,23 +22487,6 @@
         }
     }
 
-    private void dumpEnabledOverlaysLPr(PrintWriter pw) {
-        pw.println("Enabled overlay paths:");
-        final int N = mEnabledOverlayPaths.size();
-        for (int i = 0; i < N; i++) {
-            final int userId = mEnabledOverlayPaths.keyAt(i);
-            pw.println(String.format("    User %d:", userId));
-            final ArrayMap<String, ArrayList<String>> userSpecificOverlays =
-                mEnabledOverlayPaths.valueAt(i);
-            final int M = userSpecificOverlays.size();
-            for (int j = 0; j < M; j++) {
-                final String targetPackageName = userSpecificOverlays.keyAt(j);
-                final ArrayList<String> overlayPackagePaths = userSpecificOverlays.valueAt(j);
-                pw.println(String.format("        %s: %s", targetPackageName, overlayPackagePaths));
-            }
-        }
-    }
-
     private String dumpDomainString(String packageName) {
         List<IntentFilterVerificationInfo> iviList = getIntentFilterVerifications(packageName)
                 .getList();
@@ -24741,11 +24664,10 @@
                     Slog.e(TAG, "failed to find package " + targetPackageName);
                     return false;
                 }
-
-                ArrayList<String> paths = null;
-                if (overlayPackageNames != null) {
+                ArrayList<String> overlayPaths = null;
+                if (overlayPackageNames != null && overlayPackageNames.size() > 0) {
                     final int N = overlayPackageNames.size();
-                    paths = new ArrayList<>(N);
+                    overlayPaths = new ArrayList<>(N);
                     for (int i = 0; i < N; i++) {
                         final String packageName = overlayPackageNames.get(i);
                         final PackageParser.Package pkg = mPackages.get(packageName);
@@ -24753,22 +24675,17 @@
                             Slog.e(TAG, "failed to find package " + packageName);
                             return false;
                         }
-                        paths.add(pkg.baseCodePath);
+                        overlayPaths.add(pkg.baseCodePath);
                     }
                 }
 
-                ArrayMap<String, ArrayList<String>> userSpecificOverlays =
-                    mEnabledOverlayPaths.get(userId);
-                if (userSpecificOverlays == null) {
-                    userSpecificOverlays = new ArrayMap<>();
-                    mEnabledOverlayPaths.put(userId, userSpecificOverlays);
+                final PackageSetting ps = mSettings.mPackages.get(targetPackageName);
+                String[] frameworkOverlayPaths = null;
+                if (!"android".equals(targetPackageName)) {
+                    frameworkOverlayPaths =
+                            mSettings.mPackages.get("android").getOverlayPaths(userId);
                 }
-
-                if (paths != null && paths.size() > 0) {
-                    userSpecificOverlays.put(targetPackageName, paths);
-                } else {
-                    userSpecificOverlays.remove(targetPackageName);
-                }
+                ps.setOverlayPaths(overlayPaths, frameworkOverlayPaths, userId);
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 14f65eb..d17267f 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -31,6 +31,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.google.android.collect.Lists;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -329,6 +330,27 @@
         modifyUserState(userId).installReason = installReason;
     }
 
+    void setOverlayPaths(List<String> overlayPaths, String[] frameworkOverlayPaths, int userId) {
+        if (overlayPaths == null && frameworkOverlayPaths == null) {
+            modifyUserState(userId).overlayPaths = null;
+            return;
+        }
+        final List<String> paths;
+        if (frameworkOverlayPaths == null) {
+            paths = overlayPaths;
+        } else {
+            paths = Lists.newArrayList(frameworkOverlayPaths);
+            if (overlayPaths != null) {
+                paths.addAll(overlayPaths);
+            }
+        }
+        modifyUserState(userId).overlayPaths = paths.toArray(new String[paths.size()]);
+    }
+
+    String[] getOverlayPaths(int userId) {
+        return readUserState(userId).overlayPaths;
+    }
+
     /** Only use for testing. Do NOT use in production code. */
     @VisibleForTesting
     SparseArray<PackageUserState> getUserState() {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 24cbdbf..b006c2d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4858,6 +4858,15 @@
             pw.print(ps.getEnabled(user.id));
             pw.print(" instant=");
             pw.println(ps.getInstantApp(user.id));
+
+            String[] overlayPaths = ps.getOverlayPaths(user.id);
+            if (overlayPaths != null && overlayPaths.length > 0) {
+                pw.println("Overlay paths:");
+                for (String path : overlayPaths) {
+                    pw.println(path);
+                }
+            }
+
             String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
             if (lastDisabledAppCaller != null) {
                 pw.print(prefix); pw.print("    lastDisabledCaller: ");
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 28ffc94..02f2afc 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -391,7 +391,8 @@
         // First send the high-level shut down broadcast.
         mActionDone = false;
         Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
+                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         mContext.sendOrderedBroadcastAsUser(intent,
                 UserHandle.ALL, null, br, mHandler, 0, null, null);
 
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 71ecaf6..350a333 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -891,8 +891,24 @@
         return mPendingRelaunchCount > 0;
     }
 
+    boolean shouldFreezeBounds() {
+        final Task task = getTask();
+
+        // For freeform windows, we can't freeze the bounds at the moment because this would make
+        // the resizing unresponsive.
+        if (task == null || task.inFreeformWorkspace()) {
+            return false;
+        }
+
+        // We freeze the bounds while drag resizing to deal with the time between
+        // the divider/drag handle being released, and the handling it's new
+        // configuration. If we are relaunched outside of the drag resizing state,
+        // we need to be careful not to do this.
+        return getTask().isDragResizing();
+    }
+
     void startRelaunching() {
-        if (canFreezeBounds()) {
+        if (shouldFreezeBounds()) {
             freezeBounds();
         }
 
@@ -909,9 +925,8 @@
     }
 
     void finishRelaunching() {
-        if (canFreezeBounds()) {
-            unfreezeBounds();
-        }
+        unfreezeBounds();
+
         if (mPendingRelaunchCount > 0) {
             mPendingRelaunchCount--;
         } else {
@@ -926,9 +941,7 @@
         if (mPendingRelaunchCount == 0) {
             return;
         }
-        if (canFreezeBounds()) {
-            unfreezeBounds();
-        }
+        unfreezeBounds();
         mPendingRelaunchCount = 0;
     }
 
@@ -1032,14 +1045,6 @@
         }
     }
 
-    private boolean canFreezeBounds() {
-        final Task task = getTask();
-
-        // For freeform windows, we can't freeze the bounds at the moment because this would make
-        // the resizing unresponsive.
-        return task != null && !task.inFreeformWorkspace();
-    }
-
     /**
      * Freezes the task bounds. The size of this task reported the app will be fixed to the bounds
      * freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even
@@ -1064,9 +1069,10 @@
      * Unfreezes the previously frozen bounds. See {@link #freezeBounds}.
      */
     private void unfreezeBounds() {
-        if (!mFrozenBounds.isEmpty()) {
-            mFrozenBounds.remove();
+        if (mFrozenBounds.isEmpty()) {
+            return;
         }
+        mFrozenBounds.remove();
         if (!mFrozenMergedConfig.isEmpty()) {
             mFrozenMergedConfig.remove();
         }
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 7d13889..cff2fad 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -206,6 +206,10 @@
             mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth,
                     mFrom.top + mFrozenTaskHeight);
 
+            // Boost the thread priority of the animation thread while the bounds animation is
+            // 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.
@@ -316,6 +320,9 @@
             removeListener(this);
             removeUpdateListener(this);
             mRunningAnimations.remove(mTarget);
+
+            // Reset the thread priority of the animation thread after the bounds animation is done
+            updateBooster();
         }
 
         @Override
@@ -446,4 +453,9 @@
             b.resume();
         }
     }
+
+    private void updateBooster() {
+        WindowManagerService.sThreadPriorityBooster.setBoundsAnimationRunning(
+                !mRunningAnimations.isEmpty());
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ccc8f63..fbe6f94 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -210,6 +210,7 @@
     final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics();
     /** @see #computeCompatSmallestWidth(boolean, int, int, int, int) */
     private final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics();
+
     /**
      * Compat metrics computed based on {@link #mDisplayMetrics}.
      * @see #updateDisplayAndOrientation(int)
@@ -226,6 +227,7 @@
      * @see #updateRotationUnchecked(boolean)
      */
     private int mRotation = 0;
+
     /**
      * Last applied orientation of the display.
      * Constants as per {@link android.content.pm.ActivityInfo.ScreenOrientation}.
@@ -233,6 +235,7 @@
      * @see WindowManagerService#updateOrientationFromAppTokensLocked(boolean, int)
      */
     private int mLastOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
     /**
      * Flag indicating that the application is receiving an orientation that has different metrics
      * than it expected. E.g. Portrait instead of Landscape.
@@ -240,6 +243,7 @@
      * @see #updateRotationUnchecked(boolean)
      */
     private boolean mAltOrientation = false;
+
     /**
      * Orientation forced by some window. If there is no visible window that specifies orientation
      * it is set to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
@@ -247,6 +251,7 @@
      * @see NonAppWindowContainers#getOrientation()
      */
     private int mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
     /**
      * Last orientation forced by the keyguard. It is applied when keyguard is shown and is not
      * occluded.
@@ -255,6 +260,11 @@
      */
     private int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
 
+    /**
+     * Keep track of wallpaper visibility to notify changes.
+     */
+    private boolean mLastWallpaperVisible = false;
+
     private Rect mBaseDisplayRect = new Rect();
     private Rect mContentRect = new Rect();
 
@@ -314,6 +324,9 @@
     // the display's direct children should be allowed.
     private boolean mRemovingDisplay = false;
 
+    // {@code false} if this display is in the processing of being created.
+    private boolean mDisplayReady = false;
+
     private final WindowLayersController mLayersController;
     WallpaperController mWallpaperController;
     int mInputMethodAnimLayerAdjustment;
@@ -720,7 +733,6 @@
      */
     DisplayContent(Display display, WindowManagerService service,
             WindowLayersController layersController, WallpaperController wallpaperController) {
-
         if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
             throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
                     + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
@@ -748,6 +760,15 @@
 
         // Add itself as a child to the root container.
         mService.mRoot.addChild(this, null);
+
+        // TODO(b/62541591): evaluate whether this is the best spot to declare the
+        // {@link DisplayContent} ready for use.
+        mDisplayReady = true;
+    }
+
+    boolean isReady() {
+        // The display is ready when the system and the individual display are both ready.
+        return mService.mDisplayReady && mDisplayReady;
     }
 
     int getDisplayId() {
@@ -2754,6 +2775,12 @@
 
         stopDimmingIfNeeded();
 
+        final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible();
+        if (wallpaperVisible != mLastWallpaperVisible) {
+            mLastWallpaperVisible = wallpaperVisible;
+            mService.mWallpaperVisibilityListeners.notifyWallpaperVisibilityChanged(this);
+        }
+
         while (!mTmpUpdateAllDrawn.isEmpty()) {
             final AppWindowToken atoken = mTmpUpdateAllDrawn.removeLast();
             // See if any windows have been drawn, so they (and others associated with them)
diff --git a/services/core/java/com/android/server/wm/WallpaperVisibilityListeners.java b/services/core/java/com/android/server/wm/WallpaperVisibilityListeners.java
new file mode 100644
index 0000000..2c06851
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WallpaperVisibilityListeners.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.SparseArray;
+import android.view.IWallpaperVisibilityListener;
+
+/**
+ * Manages and trigger wallpaper visibility listeners.
+ */
+class WallpaperVisibilityListeners {
+
+    /**
+     * A map of displayIds and its listeners.
+     */
+    private final SparseArray<RemoteCallbackList<IWallpaperVisibilityListener>> mDisplayListeners =
+            new SparseArray<>();
+
+    void registerWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+            int displayId) {
+        RemoteCallbackList<IWallpaperVisibilityListener> listeners =
+                mDisplayListeners.get(displayId);
+        if (listeners == null) {
+            listeners = new RemoteCallbackList<>();
+            mDisplayListeners.append(displayId, listeners);
+        }
+        listeners.register(listener);
+    }
+
+    void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+            int displayId) {
+        RemoteCallbackList<IWallpaperVisibilityListener> listeners =
+                mDisplayListeners.get(displayId);
+        if (listeners == null) {
+            return;
+        }
+        listeners.unregister(listener);
+    }
+
+    void notifyWallpaperVisibilityChanged(DisplayContent displayContent) {
+        final int displayId = displayContent.getDisplayId();
+        final boolean visible = displayContent.mWallpaperController.isWallpaperVisible();
+        RemoteCallbackList<IWallpaperVisibilityListener> displayListeners =
+                mDisplayListeners.get(displayId);
+
+        // No listeners for this display.
+        if (displayListeners == null) {
+            return;
+        }
+
+        int i = displayListeners.beginBroadcast();
+        while (i > 0) {
+            i--;
+            IWallpaperVisibilityListener listener = displayListeners.getBroadcastItem(i);
+            try {
+                listener.onWallpaperVisibilityChanged(visible, displayId);
+            } catch (RemoteException e) {
+                // Nothing to do in here, RemoteCallbackListener will clean it up.
+            }
+        }
+        displayListeners.finishBroadcast();
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 03b5b827..aabf2be 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -108,7 +108,8 @@
     }
 
     void addDisplayLocked(final int displayId) {
-        // Create the DisplayContentsAnimator object by retrieving it.
+        // Create the DisplayContentsAnimator object by retrieving it if the associated
+        // {@link DisplayContent} exists.
         getDisplayContentsAnimatorLocked(displayId);
         if (displayId == DEFAULT_DISPLAY) {
             mInitialized = true;
@@ -356,8 +357,16 @@
     }
 
     private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) {
+        if (displayId < 0) {
+            return null;
+        }
+
         DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
-        if (displayAnimator == null) {
+
+        // It is possible that this underlying {@link DisplayContent} has been removed. In this
+        // case, we do not want to create an animator associated with it as {link #animate} will
+        // fail.
+        if (displayAnimator == null && mService.mRoot.getDisplayContent(displayId) != null) {
             displayAnimator = new DisplayContentsAnimator();
             mDisplayContentsAnimators.put(displayId, displayAnimator);
         }
@@ -365,8 +374,10 @@
     }
 
     void setScreenRotationAnimationLocked(int displayId, ScreenRotationAnimation animation) {
-        if (displayId >= 0) {
-            getDisplayContentsAnimatorLocked(displayId).mScreenRotationAnimation = animation;
+        final DisplayContentsAnimator animator = getDisplayContentsAnimatorLocked(displayId);
+
+        if (animator != null) {
+            animator.mScreenRotationAnimation = animation;
         }
     }
 
@@ -374,7 +385,9 @@
         if (displayId < 0) {
             return null;
         }
-        return getDisplayContentsAnimatorLocked(displayId).mScreenRotationAnimation;
+
+        DisplayContentsAnimator animator = getDisplayContentsAnimatorLocked(displayId);
+        return animator != null? animator.mScreenRotationAnimation : null;
     }
 
     void requestRemovalOfReplacedWindows(WindowState win) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3b7ec34..93116a3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -181,6 +181,7 @@
 import android.view.IOnKeyguardExitResult;
 import android.view.IPinnedStackListener;
 import android.view.IRotationWatcher;
+import android.view.IWallpaperVisibilityListener;
 import android.view.IWindow;
 import android.view.IWindowId;
 import android.view.IWindowManager;
@@ -549,9 +550,9 @@
     }
 
     class RotationWatcher {
-        IRotationWatcher mWatcher;
-        IBinder.DeathRecipient mDeathRecipient;
-        int mDisplayId;
+        final IRotationWatcher mWatcher;
+        final IBinder.DeathRecipient mDeathRecipient;
+        final int mDisplayId;
         RotationWatcher(IRotationWatcher watcher, IBinder.DeathRecipient deathRecipient,
                 int displayId) {
             mWatcher = watcher;
@@ -562,6 +563,8 @@
 
     ArrayList<RotationWatcher> mRotationWatchers = new ArrayList<>();
     int mDeferredRotationPauseCount;
+    final WallpaperVisibilityListeners mWallpaperVisibilityListeners =
+            new WallpaperVisibilityListeners();
 
     int mSystemDecorLayer = 0;
     final Rect mScreenRect = new Rect();
@@ -3937,6 +3940,29 @@
         }
     }
 
+    @Override
+    public boolean registerWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+            int displayId) {
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+            if (displayContent == null) {
+                throw new IllegalArgumentException("Trying to register visibility event "
+                        + "for invalid display: " + displayId);
+            }
+            mWallpaperVisibilityListeners.registerWallpaperVisibilityListener(listener, displayId);
+            return displayContent.mWallpaperController.isWallpaperVisible();
+        }
+    }
+
+    @Override
+    public void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+            int displayId) {
+        synchronized (mWindowMap) {
+            mWallpaperVisibilityListeners
+                    .unregisterWallpaperVisibilityListener(listener, displayId);
+        }
+    }
+
     /**
      * Apps that use the compact menu panel (as controlled by the panelMenuIsCompact
      * theme attribute) on devices that feature a physical options menu key attempt to position
@@ -5890,8 +5916,8 @@
             return;
         }
 
-        if (!mDisplayReady || !mPolicy.isScreenOn()) {
-            // No need to freeze the screen before the system is ready or if
+        if (!displayContent.isReady() || !mPolicy.isScreenOn()) {
+            // No need to freeze the screen before the display is ready, system is ready, or if
             // the screen is off.
             return;
         }
diff --git a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
index 6a244a2..1b2eb46 100644
--- a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
+++ b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
@@ -23,6 +23,7 @@
 import static com.android.server.LockGuard.INDEX_WINDOW;
 import static com.android.server.am.ActivityManagerService.TOP_APP_PRIORITY_BOOST;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.AnimationThread;
 import com.android.server.ThreadPriorityBooster;
 
@@ -32,12 +33,18 @@
  */
 class WindowManagerThreadPriorityBooster extends ThreadPriorityBooster {
 
-    private final AnimationThread mAnimationThread;
+    private final Object mLock = new Object();
+
+    private final int mAnimationThreadId;
+
+    @GuardedBy("mLock")
     private boolean mAppTransitionRunning;
+    @GuardedBy("mLock")
+    private boolean mBoundsAnimationRunning;
 
     WindowManagerThreadPriorityBooster() {
         super(THREAD_PRIORITY_DISPLAY, INDEX_WINDOW);
-        mAnimationThread = AnimationThread.get();
+        mAnimationThreadId = AnimationThread.get().getThreadId();
     }
 
     @Override
@@ -45,7 +52,7 @@
 
         // Do not boost the animation thread. As the animation thread is changing priorities,
         // boosting it might mess up the priority because we reset it the the previous priority.
-        if (myTid() == mAnimationThread.getThreadId()) {
+        if (myTid() == mAnimationThreadId) {
             return;
         }
         super.boost();
@@ -55,24 +62,35 @@
     public void reset() {
 
         // See comment in boost().
-        if (myTid() == mAnimationThread.getThreadId()) {
+        if (myTid() == mAnimationThreadId) {
             return;
         }
         super.reset();
     }
 
     void setAppTransitionRunning(boolean running) {
-        if (mAppTransitionRunning == running) {
-            return;
+        synchronized (mLock) {
+            if (mAppTransitionRunning != running) {
+                mAppTransitionRunning = running;
+                updatePriorityLocked();
+            }
         }
-
-        final int priority = calculatePriority(running);
-        setBoostToPriority(priority);
-        setThreadPriority(mAnimationThread.getThreadId(), priority);
-        mAppTransitionRunning = running;
     }
 
-    private int calculatePriority(boolean appTransitionRunning) {
-        return appTransitionRunning ? TOP_APP_PRIORITY_BOOST : THREAD_PRIORITY_DISPLAY;
+    void setBoundsAnimationRunning(boolean running) {
+        synchronized (mLock) {
+            if (mBoundsAnimationRunning != running) {
+                mBoundsAnimationRunning = running;
+                updatePriorityLocked();
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void updatePriorityLocked() {
+        int priority = (mAppTransitionRunning || mBoundsAnimationRunning)
+                ? TOP_APP_PRIORITY_BOOST : THREAD_PRIORITY_DISPLAY;
+        setBoostToPriority(priority);
+        setThreadPriority(mAnimationThreadId, priority);
     }
 }
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index 96c2d7e..470cc57 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -39,7 +39,7 @@
 
 }
 
-static void android_server_SystemServer_startHidlServices(JNIEnv* /* env */, jobject /* clazz */) {
+static void android_server_SystemServer_startHidlServices(JNIEnv* env, jobject /* clazz */) {
     using ::android::frameworks::schedulerservice::V1_0::ISchedulingPolicyService;
     using ::android::frameworks::schedulerservice::V1_0::implementation::SchedulingPolicyService;
     using ::android::frameworks::sensorservice::V1_0::ISensorManager;
@@ -50,7 +50,10 @@
 
     configureRpcThreadpool(5, false /* callerWillJoin */);
 
-    sp<ISensorManager> sensorService = new SensorManager();
+    JavaVM *vm;
+    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Cannot get Java VM");
+
+    sp<ISensorManager> sensorService = new SensorManager(vm);
     err = sensorService->registerAsService();
     ALOGE_IF(err != OK, "Cannot register %s: %d", ISensorManager::descriptor, err);
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 1d5fb55..f53eb15 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -558,6 +558,13 @@
         }
     }
 
+    boolean isRecognitionRequested(UUID modelId) {
+        synchronized (mLock) {
+            ModelData modelData = mModelDataMap.get(modelId);
+            return modelData != null && modelData.isRequested();
+        }
+    }
+
     //---- SoundTrigger.StatusListener methods
     @Override
     public void onRecognition(RecognitionEvent event) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 9bca012..51c805d 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -16,18 +16,25 @@
 
 package com.android.server.soundtrigger;
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
 
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.Manifest;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.SoundModel;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
+import android.media.soundtrigger.SoundTriggerManager;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.ParcelUuid;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.util.Slog;
 
@@ -36,6 +43,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.TreeMap;
 import java.util.UUID;
 
 /**
@@ -52,16 +60,23 @@
     private static final boolean DEBUG = true;
 
     final Context mContext;
+    private Object mLock;
     private final SoundTriggerServiceStub mServiceStub;
     private final LocalSoundTriggerService mLocalSoundTriggerService;
     private SoundTriggerDbHelper mDbHelper;
     private SoundTriggerHelper mSoundTriggerHelper;
+    private final TreeMap<UUID, SoundModel> mLoadedModels;
+    private final TreeMap<UUID, LocalSoundTriggerRecognitionStatusCallback> mIntentCallbacks;
+    private PowerManager.WakeLock mWakelock;
 
     public SoundTriggerService(Context context) {
         super(context);
         mContext = context;
         mServiceStub = new SoundTriggerServiceStub();
         mLocalSoundTriggerService = new LocalSoundTriggerService(context);
+        mLoadedModels = new TreeMap<UUID, SoundModel>();
+        mIntentCallbacks = new TreeMap<UUID, LocalSoundTriggerRecognitionStatusCallback>();
+        mLock = new Object();
     }
 
     @Override
@@ -177,8 +192,357 @@
             mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
             mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
         }
+
+        @Override
+        public int loadGenericSoundModel(GenericSoundModel soundModel) {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return STATUS_ERROR;
+            if (soundModel == null || soundModel.uuid == null) {
+                Slog.e(TAG, "Invalid sound model");
+                return STATUS_ERROR;
+            }
+            if (DEBUG) {
+                Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid);
+            }
+            synchronized (mLock) {
+                SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
+                // If the model we're loading is actually different than what we had loaded, we
+                // should unload that other model now. We don't care about return codes since we
+                // don't know if the other model is loaded.
+                if (oldModel != null && !oldModel.equals(soundModel)) {
+                    mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
+                    mIntentCallbacks.remove(soundModel.uuid);
+                }
+                mLoadedModels.put(soundModel.uuid, soundModel);
+            }
+            return STATUS_OK;
+        }
+
+        @Override
+        public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return STATUS_ERROR;
+            if (soundModel == null || soundModel.uuid == null) {
+                Slog.e(TAG, "Invalid sound model");
+                return STATUS_ERROR;
+            }
+            if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) {
+                Slog.e(TAG, "Only one keyphrase per model is currently supported.");
+                return STATUS_ERROR;
+            }
+            if (DEBUG) {
+                Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid);
+            }
+            synchronized (mLock) {
+                SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
+                // If the model we're loading is actually different than what we had loaded, we
+                // should unload that other model now. We don't care about return codes since we
+                // don't know if the other model is loaded.
+                if (oldModel != null && !oldModel.equals(soundModel)) {
+                    mSoundTriggerHelper.unloadKeyphraseSoundModel(soundModel.keyphrases[0].id);
+                    mIntentCallbacks.remove(soundModel.uuid);
+                }
+                mLoadedModels.put(soundModel.uuid, soundModel);
+            }
+            return STATUS_OK;
+        }
+
+        @Override
+        public int startRecognitionForIntent(ParcelUuid soundModelId, PendingIntent callbackIntent,
+                SoundTrigger.RecognitionConfig config) {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return STATUS_ERROR;
+            if (DEBUG) {
+                Slog.i(TAG, "startRecognition(): id = " + soundModelId);
+            }
+
+            synchronized (mLock) {
+                SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
+                if (soundModel == null) {
+                    Slog.e(TAG, soundModelId + " is not loaded");
+                    return STATUS_ERROR;
+                }
+                LocalSoundTriggerRecognitionStatusCallback callback = mIntentCallbacks.get(
+                        soundModelId.getUuid());
+                if (callback != null) {
+                    Slog.e(TAG, soundModelId + " is already running");
+                    return STATUS_ERROR;
+                }
+                callback = new LocalSoundTriggerRecognitionStatusCallback(soundModelId.getUuid(),
+                        callbackIntent, config);
+                int ret;
+                switch (soundModel.type) {
+                    case SoundModel.TYPE_KEYPHRASE: {
+                        KeyphraseSoundModel keyphraseSoundModel = (KeyphraseSoundModel) soundModel;
+                        ret = mSoundTriggerHelper.startKeyphraseRecognition(
+                                keyphraseSoundModel.keyphrases[0].id, keyphraseSoundModel, callback,
+                                config);
+                    } break;
+                    case SoundModel.TYPE_GENERIC_SOUND:
+                        ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid,
+                                (GenericSoundModel) soundModel, callback, config);
+                        break;
+                    default:
+                        Slog.e(TAG, "Unknown model type");
+                        return STATUS_ERROR;
+                }
+
+                if (ret != STATUS_OK) {
+                    Slog.e(TAG, "Failed to start model: " + ret);
+                    return ret;
+                }
+                mIntentCallbacks.put(soundModelId.getUuid(), callback);
+            }
+            return STATUS_OK;
+        }
+
+        @Override
+        public int stopRecognitionForIntent(ParcelUuid soundModelId) {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return STATUS_ERROR;
+            if (DEBUG) {
+                Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
+            }
+
+            synchronized (mLock) {
+                SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
+                if (soundModel == null) {
+                    Slog.e(TAG, soundModelId + " is not loaded");
+                    return STATUS_ERROR;
+                }
+                LocalSoundTriggerRecognitionStatusCallback callback = mIntentCallbacks.get(
+                        soundModelId.getUuid());
+                if (callback == null) {
+                    Slog.e(TAG, soundModelId + " is not running");
+                    return STATUS_ERROR;
+                }
+                int ret;
+                switch (soundModel.type) {
+                    case SoundModel.TYPE_KEYPHRASE:
+                        ret = mSoundTriggerHelper.stopKeyphraseRecognition(
+                                ((KeyphraseSoundModel)soundModel).keyphrases[0].id, callback);
+                        break;
+                    case SoundModel.TYPE_GENERIC_SOUND:
+                        ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback);
+                        break;
+                    default:
+                        Slog.e(TAG, "Unknown model type");
+                        return STATUS_ERROR;
+                }
+
+                if (ret != STATUS_OK) {
+                    Slog.e(TAG, "Failed to stop model: " + ret);
+                    return ret;
+                }
+                mIntentCallbacks.remove(soundModelId.getUuid());
+            }
+            return STATUS_OK;
+        }
+
+        @Override
+        public int unloadSoundModel(ParcelUuid soundModelId) {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return STATUS_ERROR;
+            if (DEBUG) {
+                Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
+            }
+
+            synchronized (mLock) {
+                SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
+                if (soundModel == null) {
+                    Slog.e(TAG, soundModelId + " is not loaded");
+                    return STATUS_ERROR;
+                }
+                int ret;
+                switch (soundModel.type) {
+                    case SoundModel.TYPE_KEYPHRASE:
+                        ret = mSoundTriggerHelper.unloadKeyphraseSoundModel(
+                                ((KeyphraseSoundModel)soundModel).keyphrases[0].id);
+                        break;
+                    case SoundModel.TYPE_GENERIC_SOUND:
+                        ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
+                        break;
+                    default:
+                        Slog.e(TAG, "Unknown model type");
+                        return STATUS_ERROR;
+                }
+                if (ret != STATUS_OK) {
+                    Slog.e(TAG, "Failed to unload model");
+                    return ret;
+                }
+                mLoadedModels.remove(soundModelId.getUuid());
+                return STATUS_OK;
+            }
+        }
+
+        @Override
+        public boolean isRecognitionActive(ParcelUuid parcelUuid) {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return false;
+            synchronized (mLock) {
+                LocalSoundTriggerRecognitionStatusCallback callback =
+                        mIntentCallbacks.get(parcelUuid.getUuid());
+                if (callback == null) {
+                    return false;
+                }
+                return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
+            }
+        }
     }
 
+    private final class LocalSoundTriggerRecognitionStatusCallback
+            extends IRecognitionStatusCallback.Stub {
+        private UUID mUuid;
+        private PendingIntent mCallbackIntent;
+        private RecognitionConfig mRecognitionConfig;
+
+        public LocalSoundTriggerRecognitionStatusCallback(UUID modelUuid,
+                PendingIntent callbackIntent,
+                RecognitionConfig config) {
+            mUuid = modelUuid;
+            mCallbackIntent = callbackIntent;
+            mRecognitionConfig = config;
+        }
+
+        @Override
+        public boolean pingBinder() {
+            return mCallbackIntent != null;
+        }
+
+        @Override
+        public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
+            if (mCallbackIntent == null) {
+                return;
+            }
+            grabWakeLock();
+
+            Slog.w(TAG, "Keyphrase sound trigger event: " + event);
+            Intent extras = new Intent();
+            extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
+                    SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT);
+            extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event);
+            try {
+                mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
+                if (!mRecognitionConfig.allowMultipleTriggers) {
+                    removeCallback(/*releaseWakeLock=*/false);
+                }
+            } catch (PendingIntent.CanceledException e) {
+                removeCallback(/*releaseWakeLock=*/true);
+            }
+        }
+
+        @Override
+        public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
+            if (mCallbackIntent == null) {
+                return;
+            }
+            grabWakeLock();
+
+            Slog.w(TAG, "Generic sound trigger event: " + event);
+            Intent extras = new Intent();
+            extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
+                    SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT);
+            extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event);
+            try {
+                mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
+                if (!mRecognitionConfig.allowMultipleTriggers) {
+                    removeCallback(/*releaseWakeLock=*/false);
+                }
+            } catch (PendingIntent.CanceledException e) {
+                removeCallback(/*releaseWakeLock=*/true);
+            }
+        }
+
+        @Override
+        public void onError(int status) {
+            if (mCallbackIntent == null) {
+                return;
+            }
+            grabWakeLock();
+
+            Slog.i(TAG, "onError: " + status);
+            Intent extras = new Intent();
+            extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
+                    SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_ERROR);
+            extras.putExtra(SoundTriggerManager.EXTRA_STATUS, status);
+            try {
+                mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
+                // Remove the callback, but wait for the intent to finish before we let go of the
+                // wake lock
+                removeCallback(/*releaseWakeLock=*/false);
+            } catch (PendingIntent.CanceledException e) {
+                removeCallback(/*releaseWakeLock=*/true);
+            }
+        }
+
+        @Override
+        public void onRecognitionPaused() {
+            if (mCallbackIntent == null) {
+                return;
+            }
+            grabWakeLock();
+
+            Slog.i(TAG, "onRecognitionPaused");
+            Intent extras = new Intent();
+            extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
+                    SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_PAUSED);
+            try {
+                mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
+            } catch (PendingIntent.CanceledException e) {
+                removeCallback(/*releaseWakeLock=*/true);
+            }
+        }
+
+        @Override
+        public void onRecognitionResumed() {
+            if (mCallbackIntent == null) {
+                return;
+            }
+            grabWakeLock();
+
+            Slog.i(TAG, "onRecognitionResumed");
+            Intent extras = new Intent();
+            extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
+                    SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_RESUMED);
+            try {
+                mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
+            } catch (PendingIntent.CanceledException e) {
+                removeCallback(/*releaseWakeLock=*/true);
+            }
+        }
+
+        private void removeCallback(boolean releaseWakeLock) {
+            mCallbackIntent = null;
+            synchronized (mLock) {
+                mIntentCallbacks.remove(mUuid);
+                if (releaseWakeLock) {
+                    mWakelock.release();
+                }
+            }
+        }
+    }
+
+    private void grabWakeLock() {
+        synchronized (mLock) {
+            if (mWakelock == null) {
+                PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
+                mWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+            }
+            mWakelock.acquire();
+        }
+    }
+
+    private PendingIntent.OnFinished mCallbackCompletedHandler = new PendingIntent.OnFinished() {
+        @Override
+        public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
+                String resultData, Bundle resultExtras) {
+            // We're only ever invoked when the callback is done, so release the lock.
+            synchronized (mLock) {
+                mWakelock.release();
+            }
+        }
+    };
+
     public final class LocalSoundTriggerService extends SoundTriggerInternal {
         private final Context mContext;
         private SoundTriggerHelper mSoundTriggerHelper;
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index 640c9e1..de205380 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -325,7 +325,8 @@
         return sEventManager;
     }
 
-    private static SessionManager getSessionManager() {
+    @VisibleForTesting
+    public static SessionManager getSessionManager() {
         // Checking for null again outside of synchronization because we only need to synchronize
         // during the lazy loading of the session logger. We don't need to synchronize elsewhere.
         if (sSessionManager == null) {
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 331328d..b1eedf5 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -369,6 +369,15 @@
     public static final String EXTRA_IS_HANDOVER = "android.telecom.extra.IS_HANDOVER";
 
     /**
+     * Parcelable extra used with {@link #EXTRA_IS_HANDOVER} to indicate the source
+     * {@link PhoneAccountHandle} when initiating a handover which {@link ConnectionService}
+     * the handover is from.
+     * @hide
+     */
+    public static final String EXTRA_HANDOVER_FROM_PHONE_ACCOUNT =
+            "android.telecom.extra.HANDOVER_FROM_PHONE_ACCOUNT";
+
+    /**
      * Extra key specified in the {@link ConnectionRequest#getExtras()} when Telecom calls
      * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}
      * to inform the {@link ConnectionService} what the initial {@link CallAudioState} of the
diff --git a/tests/JobSchedulerTestApp/res/layout/activity_main.xml b/tests/JobSchedulerTestApp/res/layout/activity_main.xml
index 96e1641..41f9777 100644
--- a/tests/JobSchedulerTestApp/res/layout/activity_main.xml
+++ b/tests/JobSchedulerTestApp/res/layout/activity_main.xml
@@ -73,10 +73,18 @@
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:orientation="horizontal">
+                    <RadioButton android:id="@+id/checkbox_none"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/none"/>
                     <RadioButton android:id="@+id/checkbox_any"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:text="@string/any"/>
+                    <RadioButton android:id="@+id/checkbox_metered"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/metered"/>
                     <RadioButton android:id="@+id/checkbox_unmetered"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
diff --git a/tests/JobSchedulerTestApp/res/values/strings.xml b/tests/JobSchedulerTestApp/res/values/strings.xml
index 90dd2b6..866b61e 100644
--- a/tests/JobSchedulerTestApp/res/values/strings.xml
+++ b/tests/JobSchedulerTestApp/res/values/strings.xml
@@ -30,7 +30,9 @@
     <string name="persisted_caption">Persisted:</string>
     <string name="constraints">Constraints</string>
     <string name="connectivity">Connectivity:</string>
+    <string name="none">None</string>
     <string name="any">Any</string>
+    <string name="metered">Metered</string>
     <string name="unmetered">WiFi</string>
     <string name="timing">Timing:</string>
     <string name="delay">Delay:</string>
diff --git a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java
index 51cdbb5..3dfdba7 100644
--- a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java
+++ b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java
@@ -63,6 +63,7 @@
         mDeadlineEditText = findViewById(R.id.deadline_time);
         mWiFiConnectivityRadioButton = findViewById(R.id.checkbox_unmetered);
         mAnyConnectivityRadioButton = findViewById(R.id.checkbox_any);
+        mCellConnectivityRadioButton = findViewById(R.id.checkbox_metered);
         mRequiresChargingCheckBox = findViewById(R.id.checkbox_charging);
         mRequiresIdleCheckbox = findViewById(R.id.checkbox_idle);
         mIsPersistedCheckbox = findViewById(R.id.checkbox_persisted);
@@ -85,6 +86,7 @@
     EditText mDeadlineEditText;
     RadioButton mWiFiConnectivityRadioButton;
     RadioButton mAnyConnectivityRadioButton;
+    RadioButton mCellConnectivityRadioButton;
     CheckBox mRequiresChargingCheckBox;
     CheckBox mRequiresIdleCheckbox;
     CheckBox mIsPersistedCheckbox;
@@ -141,9 +143,12 @@
             builder.setOverrideDeadline(Long.parseLong(deadline) * 1000);
         }
         boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();
+        boolean requiresMetered = mCellConnectivityRadioButton.isChecked();
         boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();
         if (requiresUnmetered) {
             builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
+        } else if (requiresMetered) {
+            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
         } else if (requiresAnyConnectivity) {
             builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
         }
diff --git a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java
index 9df11fe..b698a3a 100644
--- a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java
+++ b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java
@@ -81,7 +81,8 @@
 
     @Override
     public boolean onStartJob(JobParameters params) {
-        Log.i(TAG, "on start job: " + params.getJobId());
+        Log.i(TAG, "on start job: " + params.getJobId()
+                + " deadline?=" + params.isOverrideDeadlineExpired());
         currentId++;
         jobParamsMap.put(currentId, params);
         final int currId = this.currentId;
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 088cbc6..7972d06 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -19,6 +19,7 @@
 
 import android.content.pm.ParceledListSlice;
 
+import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 
 import android.net.wifi.WifiConfiguration;
@@ -61,6 +62,8 @@
 
     WifiConfiguration getMatchingWifiConfig(in ScanResult scanResult);
 
+    List<OsuProvider> getMatchingOsuProviders(in ScanResult scanResult);
+
     int addOrUpdateNetwork(in WifiConfiguration config);
 
     boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index c89a9a4..ec8d91b 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -30,6 +30,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.os.Binder;
 import android.os.Build;
@@ -1000,11 +1001,9 @@
     /**
      * Returns a WifiConfiguration matching this ScanResult
      *
-     * An {@link UnsupportedOperationException} will be thrown if Passpoint is not enabled
-     * on the device.
-     *
      * @param scanResult scanResult that represents the BSSID
      * @return {@link WifiConfiguration} that matches this BSSID or null
+     * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
      * @hide
      */
     public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
@@ -1016,6 +1015,24 @@
     }
 
     /**
+     * Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP.
+     *
+     * An empty list will be returned if no match is found.
+     *
+     * @param scanResult scanResult that represents the BSSID
+     * @return list of {@link OsuProvider}
+     * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
+     * @hide
+     */
+    public List<OsuProvider> getMatchingOsuProviders(ScanResult scanResult) {
+        try {
+            return mService.getMatchingOsuProviders(scanResult);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Add a new network description to the set of configured networks.
      * The {@code networkId} field of the supplied configuration object
      * is ignored.