Merge "Properly refer to Java "long" as "jlong""
diff --git a/Android.mk b/Android.mk
index e405345..65d4d24 100644
--- a/Android.mk
+++ b/Android.mk
@@ -78,6 +78,7 @@
 update-api: doc-comment-check-docs
 
 # ==== hiddenapi lists =======================================
+ifneq ($(UNSAFE_DISABLE_HIDDENAPI_FLAGS),true)
 .KATI_RESTAT: $(INTERNAL_PLATFORM_HIDDENAPI_FLAGS)
 $(INTERNAL_PLATFORM_HIDDENAPI_FLAGS): \
     PRIVATE_FLAGS_INPUTS := $(PRIVATE_FLAGS_INPUTS) $(SOONG_HIDDENAPI_FLAGS)
@@ -108,6 +109,7 @@
 
 $(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_FLAGS))
 $(call dist-for-goals,droidcore,$(INTERNAL_PLATFORM_HIDDENAPI_GREYLIST_METADATA))
+endif  # UNSAFE_DISABLE_HIDDENAPI_FLAGS
 
 # Include subdirectory makefiles
 # ============================================================
diff --git a/api/current.txt b/api/current.txt
index b65150c..226e58d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -25506,7 +25506,7 @@
     method @Nullable public android.media.MediaTimestamp getTimestamp();
     method @NonNull public java.util.List<android.media.MediaPlayer2.TrackInfo> getTrackInfo();
     method @NonNull public java.util.List<android.media.MediaPlayer2.TrackInfo> getTrackInfo(@NonNull android.media.DataSourceDesc);
-    method public android.media.VideoSize getVideoSize();
+    method public android.util.Size getVideoSize();
     method public boolean isLooping();
     method public Object loopCurrent(boolean);
     method public Object notifyWhenCommandLabelReached(@NonNull Object);
@@ -25648,7 +25648,7 @@
     method public void onMediaTimeDiscontinuity(@NonNull android.media.MediaPlayer2, @NonNull android.media.DataSourceDesc, @NonNull android.media.MediaTimestamp);
     method public void onSubtitleData(@NonNull android.media.MediaPlayer2, @NonNull android.media.DataSourceDesc, @NonNull android.media.SubtitleData);
     method public void onTimedMetaDataAvailable(@NonNull android.media.MediaPlayer2, @NonNull android.media.DataSourceDesc, @NonNull android.media.TimedMetaData);
-    method public void onVideoSizeChanged(@NonNull android.media.MediaPlayer2, @NonNull android.media.DataSourceDesc, @NonNull android.media.VideoSize);
+    method public void onVideoSizeChanged(@NonNull android.media.MediaPlayer2, @NonNull android.media.DataSourceDesc, @NonNull android.util.Size);
   }
 
   public static final class MediaPlayer2.MetricsConstants {
@@ -26553,11 +26553,6 @@
     method @NonNull public android.media.UriDataSourceDesc.Builder setDataSource(@NonNull android.content.Context, @NonNull android.net.Uri, @Nullable java.util.Map<java.lang.String,java.lang.String>, @Nullable java.util.List<java.net.HttpCookie>);
   }
 
-  public final class VideoSize {
-    method public int getHeight();
-    method public int getWidth();
-  }
-
   public interface VolumeAutomation {
     method @NonNull public android.media.VolumeShaper createVolumeShaper(@NonNull android.media.VolumeShaper.Configuration);
   }
@@ -50366,6 +50361,7 @@
     method public final int getScrollX();
     method public final int getScrollY();
     method @android.view.ViewDebug.ExportedProperty(category="drawing") @ColorInt public int getSolidColor();
+    method @LayoutRes public int getSourceLayoutResId();
     method public android.animation.StateListAnimator getStateListAnimator();
     method protected int getSuggestedMinimumHeight();
     method protected int getSuggestedMinimumWidth();
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 24d3b98..428c9b0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -10591,14 +10591,19 @@
     }
 
     /**
-     * Returns if a package is whitelisted to access cross-profile calendar APIs.
+     * Returns if a package is allowed to access cross-profile calendar APIs.
+     *
+     * <p>A package is allowed to access cross-profile calendar APIs if it's allowed by
+     * admins via {@link #setCrossProfileCalendarPackages(ComponentName, Set)} and
+     * {@link android.provider.Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED}
+     * is turned on in the managed profile.
      *
      * <p>To query for a specific user, use
      * {@link Context#createPackageContextAsUser(String, int, UserHandle)} to create a context for
      * that user, and get a {@link DevicePolicyManager} from this context.
      *
      * @param packageName the name of the package
-     * @return {@code true} if the package is whitelisted to access cross-profile calendar APIs.
+     * @return {@code true} if the package is allowed to access cross-profile calendar APIs.
      * {@code false} otherwise.
      *
      * @see #setCrossProfileCalendarPackages(ComponentName, Set)
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 1358bc2..b27c5dc 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -640,19 +640,15 @@
     public static final int PRIVATE_FLAG_HAS_FRAGILE_USER_DATA = 1 << 24;
 
     /**
-     * Indicate whether this application prefers code integrity, that is, run only code that is
-     * signed. This requires android:extractNativeLibs to be "false", as well as .dex and .so (if
-     * any) stored uncompressed inside the APK, which is signed. At run time, the implications
-     * include:
-     *
-     * <ul>
-     * <li>ART will JIT the dex code directly from the APK. There may be performance characteristic
-     * changes depend on the actual workload.
-     * </ul>
+     * Indicates whether this application wants to use the embedded dex in the APK, rather than
+     * extracted or locally compiled variants. This keeps the dex code protected by the APK
+     * signature. Such apps will always run in JIT mode (same when they are first installed), and
+     * the system will never generate ahead-of-time compiled code for them. Depending on the app's
+     * workload, there may be some run time performance change, noteably the cold start time.
      *
      * @hide
      */
-    public static final int PRIVATE_FLAG_PREFER_CODE_INTEGRITY = 1 << 25;
+    public static final int PRIVATE_FLAG_USE_EMBEDDED_DEX = 1 << 25;
 
     /** @hide */
     @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = {
@@ -669,7 +665,7 @@
             PRIVATE_FLAG_ISOLATED_SPLIT_LOADING,
             PRIVATE_FLAG_OEM,
             PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE,
-            PRIVATE_FLAG_PREFER_CODE_INTEGRITY,
+            PRIVATE_FLAG_USE_EMBEDDED_DEX,
             PRIVATE_FLAG_PRIVILEGED,
             PRIVATE_FLAG_PRODUCT,
             PRIVATE_FLAG_PRODUCT_SERVICES,
@@ -1962,8 +1958,8 @@
     }
 
     /** @hide */
-    public boolean isCodeIntegrityPreferred() {
-        return (privateFlags & PRIVATE_FLAG_PREFER_CODE_INTEGRITY) != 0;
+    public boolean isEmbeddedDexUsed() {
+        return (privateFlags & PRIVATE_FLAG_USE_EMBEDDED_DEX) != 0;
     }
 
     /**
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 4f674bd..8b8f3e5 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1290,6 +1290,28 @@
             isStaged = source.readBoolean();
         }
 
+        /** {@hide} */
+        public SessionParams copy() {
+            SessionParams ret = new SessionParams(mode);
+            ret.installFlags = installFlags;
+            ret.installLocation = installLocation;
+            ret.installReason = installReason;
+            ret.sizeBytes = sizeBytes;
+            ret.appPackageName = appPackageName;
+            ret.appIcon = appIcon;  // not a copy.
+            ret.appLabel = appLabel;
+            ret.originatingUri = originatingUri;  // not a copy, but immutable.
+            ret.originatingUid = originatingUid;
+            ret.referrerUri = referrerUri;  // not a copy, but immutable.
+            ret.abiOverride = abiOverride;
+            ret.volumeUuid = volumeUuid;
+            ret.grantedRuntimePermissions = grantedRuntimePermissions;
+            ret.installerPackageName = installerPackageName;
+            ret.isMultiPackage = isMultiPackage;
+            ret.isStaged = isStaged;
+            return ret;
+        }
+
         /**
          * Check if there are hidden options set.
          *
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1fab443..8578b4a 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -475,7 +475,7 @@
         public final boolean extractNativeLibs;
         public final boolean isolatedSplits;
         public final boolean isSplitRequired;
-        public final boolean preferCodeIntegrity;
+        public final boolean useEmbeddedDex;
 
         public ApkLite(String codePath, String packageName, String splitName,
                 boolean isFeatureSplit,
@@ -484,7 +484,7 @@
                 int revisionCode, int installLocation, List<VerifierInfo> verifiers,
                 SigningDetails signingDetails, boolean coreApp,
                 boolean debuggable, boolean multiArch, boolean use32bitAbi,
-                boolean preferCodeIntegrity, boolean extractNativeLibs, boolean isolatedSplits) {
+                boolean useEmbeddedDex, boolean extractNativeLibs, boolean isolatedSplits) {
             this.codePath = codePath;
             this.packageName = packageName;
             this.splitName = splitName;
@@ -501,7 +501,7 @@
             this.debuggable = debuggable;
             this.multiArch = multiArch;
             this.use32bitAbi = use32bitAbi;
-            this.preferCodeIntegrity = preferCodeIntegrity;
+            this.useEmbeddedDex = useEmbeddedDex;
             this.extractNativeLibs = extractNativeLibs;
             this.isolatedSplits = isolatedSplits;
             this.isSplitRequired = isSplitRequired;
@@ -1726,7 +1726,7 @@
         boolean isolatedSplits = false;
         boolean isFeatureSplit = false;
         boolean isSplitRequired = false;
-        boolean preferCodeIntegrity = false;
+        boolean useEmbeddedDex = false;
         String configForSplit = null;
         String usesSplitName = null;
 
@@ -1790,8 +1790,8 @@
                         extractNativeLibsProvided = Boolean.valueOf(
                                 attrs.getAttributeBooleanValue(i, true));
                     }
-                    if ("preferCodeIntegrity".equals(attr)) {
-                        preferCodeIntegrity = attrs.getAttributeBooleanValue(i, false);
+                    if ("useEmbeddedDex".equals(attr)) {
+                        useEmbeddedDex = attrs.getAttributeBooleanValue(i, false);
                     }
                 }
             } else if (TAG_USES_SPLIT.equals(parser.getName())) {
@@ -1851,16 +1851,10 @@
         final boolean extractNativeLibs = (extractNativeLibsProvided != null)
                 ? extractNativeLibsProvided : extractNativeLibsDefault;
 
-        if (preferCodeIntegrity && extractNativeLibs) {
-            throw new PackageParserException(
-                    PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
-                    "Can't request both preferCodeIntegrity and extractNativeLibs");
-        }
-
         return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
                 configForSplit, usesSplitName, isSplitRequired, versionCode, versionCodeMajor,
                 revisionCode, installLocation, verifiers, signingDetails, coreApp, debuggable,
-                multiArch, use32bitAbi, preferCodeIntegrity, extractNativeLibs, isolatedSplits);
+                multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs, isolatedSplits);
     }
 
     /**
@@ -3789,9 +3783,9 @@
         }
 
         if (sa.getBoolean(
-                R.styleable.AndroidManifestApplication_preferCodeIntegrity,
+                R.styleable.AndroidManifestApplication_useEmbeddedDex,
                 false)) {
-            ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PREFER_CODE_INTEGRITY;
+            ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_USE_EMBEDDED_DEX;
         }
 
         if (sa.getBoolean(
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index baf64ad..59db49e 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1394,6 +1394,17 @@
         mResourcesImpl.getValue(name, outValue, resolveRefs);
     }
 
+
+    /**
+     * @param set AttributeSet for which we want to find the source.
+     * @return The resource id for the source that is backing the given AttributeSet
+     * @hide
+     */
+    @AnyRes
+    public static int getAttributeSetSourceResId(@Nullable AttributeSet set) {
+        return ResourcesImpl.getAttributeSetSourceResId(set);
+    }
+
     /**
      * This class holds the current attribute values for a particular theme.
      * In other words, a Theme is a set of values for resource attributes;
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index d8564d5..9898079 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -15,6 +15,8 @@
  */
 package android.content.res;
 
+import static android.content.res.Resources.ID_NULL;
+
 import android.animation.Animator;
 import android.animation.StateListAnimator;
 import android.annotation.AnyRes;
@@ -1222,7 +1224,7 @@
                     for (int i = 0; i < num; i++) {
                         if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null
                                 && cachedXmlBlockFiles[i].equals(file)) {
-                            return cachedXmlBlocks[i].newParser();
+                            return cachedXmlBlocks[i].newParser(id);
                         }
                     }
 
@@ -1239,7 +1241,7 @@
                         cachedXmlBlockCookies[pos] = assetCookie;
                         cachedXmlBlockFiles[pos] = file;
                         cachedXmlBlocks[pos] = block;
-                        return block.newParser();
+                        return block.newParser(id);
                     }
                 }
             } catch (Exception e) {
@@ -1299,6 +1301,14 @@
         }
     }
 
+    @AnyRes
+    static int getAttributeSetSourceResId(@Nullable AttributeSet set) {
+        if (set == null) {
+            return ID_NULL;
+        }
+        return ((XmlBlock.Parser) set).getSourceResId();
+    }
+
     LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() {
         return sPreloadedDrawables[0];
     }
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index 4e1159a..d8c3dde 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -16,6 +16,9 @@
 
 package android.content.res;
 
+import static android.content.res.Resources.ID_NULL;
+
+import android.annotation.AnyRes;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.util.TypedValue;
@@ -73,19 +76,29 @@
 
     @UnsupportedAppUsage
     public XmlResourceParser newParser() {
+        return newParser(ID_NULL);
+    }
+
+    public XmlResourceParser newParser(@AnyRes int resId) {
         synchronized (this) {
             if (mNative != 0) {
-                return new Parser(nativeCreateParseState(mNative), this);
+                return new Parser(nativeCreateParseState(mNative), this, resId);
             }
             return null;
         }
     }
 
     /*package*/ final class Parser implements XmlResourceParser {
-        Parser(long parseState, XmlBlock block) {
+        Parser(long parseState, XmlBlock block, @AnyRes int sourceResId) {
             mParseState = parseState;
             mBlock = block;
             block.mOpenCount++;
+            mSourceResId = sourceResId;
+        }
+
+        @AnyRes
+        public int getSourceResId() {
+            return mSourceResId;
         }
 
         public void setFeature(String name, boolean state) throws XmlPullParserException {
@@ -473,6 +486,7 @@
         private boolean mDecNextDepth = false;
         private int mDepth = 0;
         private int mEventType = START_DOCUMENT;
+        private @AnyRes int mSourceResId;
     }
 
     protected void finalize() throws Throwable {
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 6061cb2..6a290b7 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -414,8 +414,7 @@
 
         // Make sure the application allows code generation
         ApplicationInfo appInfo = mContext.getApplicationInfo();
-        if ((appInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PREFER_CODE_INTEGRITY) != 0
-            || appInfo.isPrivilegedApp()) {
+        if (appInfo.isEmbeddedDexUsed() || appInfo.isPrivilegedApp()) {
             mUseCompiledView = false;
             return;
         }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 6e76647..8061cc3 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -155,6 +155,8 @@
     private static native int nativeGetActiveConfig(IBinder displayToken);
     private static native boolean nativeSetActiveConfig(IBinder displayToken, int id);
     private static native int[] nativeGetDisplayColorModes(IBinder displayToken);
+    private static native SurfaceControl.DisplayPrimaries nativeGetDisplayNativePrimaries(
+            IBinder displayToken);
     private static native int[] nativeGetCompositionDataspaces();
     private static native int nativeGetActiveColorMode(IBinder displayToken);
     private static native boolean nativeSetActiveColorMode(IBinder displayToken,
@@ -1536,6 +1538,73 @@
     }
 
     /**
+     * Color coordinates in CIE1931 XYZ color space
+     *
+     * @hide
+     */
+    public static final class CieXyz {
+        /**
+         * @hide
+         */
+        public float X;
+
+        /**
+         * @hide
+         */
+        public float Y;
+
+        /**
+         * @hide
+         */
+        public float Z;
+    }
+
+    /**
+     * Contains a display's color primaries
+     *
+     * @hide
+     */
+    public static final class DisplayPrimaries {
+        /**
+         * @hide
+         */
+        public CieXyz red;
+
+        /**
+         * @hide
+         */
+        public CieXyz green;
+
+        /**
+         * @hide
+         */
+        public CieXyz blue;
+
+        /**
+         * @hide
+         */
+        public CieXyz white;
+
+        /**
+         * @hide
+         */
+        public DisplayPrimaries() {
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static SurfaceControl.DisplayPrimaries getDisplayNativePrimaries(
+            IBinder displayToken) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+
+        return nativeGetDisplayNativePrimaries(displayToken);
+    }
+
+    /**
      * @hide
      */
     public static int getActiveColorMode(IBinder displayToken) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 519181d..ec8ed5b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.content.res.Resources.ID_NULL;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
 
@@ -5052,6 +5053,9 @@
     @Nullable
     private WeakReference<ContentCaptureSession> mContentCaptureSession;
 
+    @LayoutRes
+    private int mSourceLayoutId = ID_NULL;
+
     /**
      * Simple constructor to use when creating a view from code.
      *
@@ -5217,6 +5221,8 @@
     public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         this(context);
 
+        mSourceLayoutId = Resources.getAttributeSetSourceResId(attrs);
+
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
 
@@ -23253,6 +23259,18 @@
     }
 
     /**
+     * A {@link View} can be inflated from an XML layout. For such Views this method returns the
+     * resource ID of the source layout.
+     *
+     * @return The layout resource id if this view was inflated from XML, otherwise
+     * {@link Resources#ID_NULL}.
+     */
+    @LayoutRes
+    public int getSourceLayoutResId() {
+        return mSourceLayoutId;
+    }
+
+    /**
      * Returns the top padding of this view.
      *
      * @return the top padding in pixels
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index c38566b..e9d72a2 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -4489,6 +4489,13 @@
         // when selecting text when the handles jump to the end / start of words which may be on
         // a different line.
         protected int mPreviousLineTouched = UNSET_LINE;
+        // The raw x coordinate of the motion down event which started the current dragging session.
+        // Only used and stored when magnifier is used.
+        private float mCurrentDragInitialTouchRawX = UNSET_X_VALUE;
+        // The scale transform applied by containers to the TextView. Only used and computed
+        // when magnifier is used.
+        private float mTextViewScaleX;
+        private float mTextViewScaleY;
 
         private HandleView(Drawable drawableLtr, Drawable drawableRtl, final int id) {
             super(mTextView.getContext());
@@ -4808,25 +4815,44 @@
                             / mMagnifierAnimator.mMagnifier.getZoom());
             final Paint.FontMetrics fontMetrics = mTextView.getPaint().getFontMetrics();
             final float glyphHeight = fontMetrics.descent - fontMetrics.ascent;
-            return glyphHeight > magnifierContentHeight;
+            return glyphHeight * mTextViewScaleY > magnifierContentHeight;
         }
 
-        private boolean viewIsMatrixTransformed() {
+        /**
+         * Traverses the hierarchy above the text view, and computes the total scale applied
+         * to it. If a rotation is encountered, the method returns {@code false}, indicating
+         * that the magnifier should not be shown anyways. It would be nice to keep these two
+         * pieces of logic separate (the rotation check and the total scale calculation),
+         * but for efficiency we can do them in a single go.
+         * @return whether the text view is rotated
+         */
+        private boolean checkForTransforms() {
             if (mMagnifierAnimator.mMagnifierIsShowing) {
                 // Do not check again when the magnifier is currently showing.
-                return false;
-            }
-            if (!mTextView.hasIdentityMatrix()) {
                 return true;
             }
+
+            if (mTextView.getRotation() != 0f || mTextView.getRotationX() != 0f
+                    || mTextView.getRotationY() != 0f) {
+                return false;
+            }
+            mTextViewScaleX = mTextView.getScaleX();
+            mTextViewScaleY = mTextView.getScaleY();
+
             ViewParent viewParent = mTextView.getParent();
             while (viewParent != null) {
-                if (viewParent instanceof View && !((View) viewParent).hasIdentityMatrix()) {
-                    return true;
+                if (viewParent instanceof View) {
+                    final View view = (View) viewParent;
+                    if (view.getRotation() != 0f || view.getRotationX() != 0f
+                            || view.getRotationY() != 0f) {
+                        return false;
+                    }
+                    mTextViewScaleX *= view.getScaleX();
+                    mTextViewScaleY *= view.getScaleY();
                 }
                 viewParent = viewParent.getParent();
             }
-            return false;
+            return true;
         }
 
         /**
@@ -4864,6 +4890,12 @@
                 return false;
             }
 
+            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                mCurrentDragInitialTouchRawX = event.getRawX();
+            } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+                mCurrentDragInitialTouchRawX = UNSET_X_VALUE;
+            }
+
             final Layout layout = mTextView.getLayout();
             final int lineNumber = layout.getLineForOffset(offset);
             // Compute whether the selection handles are currently on the same line, and,
@@ -4891,6 +4923,8 @@
             } else {
                 rightBound += mTextView.getLayout().getLineRight(lineNumber);
             }
+            leftBound *= mTextViewScaleX;
+            rightBound *= mTextViewScaleX;
             final float contentWidth = Math.round(mMagnifierAnimator.mMagnifier.getWidth()
                     / mMagnifierAnimator.mMagnifier.getZoom());
             if (touchXInView < leftBound - contentWidth / 2
@@ -4898,13 +4932,27 @@
                 // The touch is too far from the current line / selection, so hide the magnifier.
                 return false;
             }
-            showPosInView.x = Math.max(leftBound, Math.min(rightBound, touchXInView));
+
+            final float scaledTouchXInView;
+            if (mTextViewScaleX == 1f) {
+                // In the common case, do not use mCurrentDragInitialTouchRawX to compute this
+                // coordinate, although the formula on the else branch should be equivalent.
+                // Since the formula relies on mCurrentDragInitialTouchRawX being set on
+                // MotionEvent.ACTION_DOWN, this makes us more defensive against cases when
+                // the sequence of events might not look as expected: for example, a sequence of
+                // ACTION_MOVE not preceded by ACTION_DOWN.
+                scaledTouchXInView = touchXInView;
+            } else {
+                scaledTouchXInView = (event.getRawX() - mCurrentDragInitialTouchRawX)
+                        * mTextViewScaleX + mCurrentDragInitialTouchRawX
+                        - textViewLocationOnScreen[0];
+            }
+            showPosInView.x = Math.max(leftBound, Math.min(rightBound, scaledTouchXInView));
 
             // Vertically snap to middle of current line.
-            showPosInView.y = (mTextView.getLayout().getLineTop(lineNumber)
+            showPosInView.y = ((mTextView.getLayout().getLineTop(lineNumber)
                     + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f
-                    + mTextView.getTotalPaddingTop() - mTextView.getScrollY();
-
+                    + mTextView.getTotalPaddingTop() - mTextView.getScrollY()) * mTextViewScaleY;
             return true;
         }
 
@@ -4956,8 +5004,8 @@
             }
 
             final PointF showPosInView = new PointF();
-            final boolean shouldShow = !tooLargeTextForMagnifier()
-                    && !viewIsMatrixTransformed()
+            final boolean shouldShow = checkForTransforms() /*check not rotated and compute scale*/
+                    && !tooLargeTextForMagnifier()
                     && obtainMagnifierShowCoordinates(event, showPosInView);
             if (shouldShow) {
                 // Make the cursor visible and stop blinking.
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 46f42f7..cfe2939 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -25,6 +25,7 @@
 import android.app.prediction.AppTargetEvent;
 import android.app.prediction.AppTargetId;
 import android.content.ClipData;
+import android.content.ClipboardManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -80,11 +81,13 @@
 import android.view.ViewGroup.LayoutParams;
 import android.widget.AbsListView;
 import android.widget.BaseAdapter;
+import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.Space;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -350,6 +353,50 @@
         super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
                 null, false);
 
+        Button copyButton = findViewById(R.id.copy_button);
+        copyButton.setOnClickListener(view -> {
+            Intent targetIntent = getTargetIntent();
+            if (targetIntent == null) {
+                finish();
+            } else {
+                final String action = targetIntent.getAction();
+
+                ClipData clipData = null;
+                if (Intent.ACTION_SEND.equals(action)) {
+                    String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
+                    Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
+
+                    if (extraText != null) {
+                        clipData = ClipData.newPlainText(null, extraText);
+                    } else if (extraStream != null) {
+                        clipData = ClipData.newUri(getContentResolver(), null, extraStream);
+                    } else {
+                        Log.w(TAG, "No data available to copy to clipboard");
+                        return;
+                    }
+                } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+                    final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
+                            Intent.EXTRA_STREAM);
+                    clipData = ClipData.newUri(getContentResolver(), null, streams.get(0));
+                    for (int i = 1; i < streams.size(); i++) {
+                        clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i)));
+                    }
+                } else {
+                    // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
+                    // so warn about unexpected action
+                    Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
+                    return;
+                }
+
+                ClipboardManager clipboardManager = (ClipboardManager) getSystemService(
+                        Context.CLIPBOARD_SERVICE);
+                clipboardManager.setPrimaryClip(clipData);
+                Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
+
+                finish();
+            }
+        });
+
         MetricsLogger.action(this, MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN);
 
         mChooserShownTime = System.currentTimeMillis();
@@ -414,39 +461,39 @@
     private void showDefaultContentPreview(final ViewGroup parentLayout,
             final Intent targetIntent) {
         CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
-        TextView previewTextView = findViewById(R.id.content_preview_text);
         if (sharingText == null) {
-            previewTextView.setVisibility(View.GONE);
+            findViewById(R.id.content_preview_text_layout).setVisibility(View.GONE);
         } else {
-            previewTextView.setText(sharingText);
+            TextView textView = findViewById(R.id.content_preview_text);
+            textView.setText(sharingText);
         }
 
         String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
-        TextView previewTitleView = findViewById(R.id.content_preview_title);
-        if (previewTitle == null) {
-            previewTitleView.setVisibility(View.GONE);
+        if (previewTitle == null || previewTitle.trim().isEmpty()) {
+            findViewById(R.id.content_preview_title_layout).setVisibility(View.GONE);
         } else {
+            TextView previewTitleView = findViewById(R.id.content_preview_title);
             previewTitleView.setText(previewTitle);
-        }
 
-        ClipData previewData = targetIntent.getClipData();
-        Uri previewThumbnail = null;
-        if (previewData != null) {
-            if (previewData.getItemCount() > 0) {
-                ClipData.Item previewDataItem = previewData.getItemAt(0);
-                previewThumbnail = previewDataItem.getUri();
+            ClipData previewData = targetIntent.getClipData();
+            Uri previewThumbnail = null;
+            if (previewData != null) {
+                if (previewData.getItemCount() > 0) {
+                    ClipData.Item previewDataItem = previewData.getItemAt(0);
+                    previewThumbnail = previewDataItem.getUri();
+                }
             }
-        }
 
-        ImageView previewThumbnailView = findViewById(R.id.content_preview_thumbnail);
-        if (previewThumbnail == null) {
-            previewThumbnailView.setVisibility(View.GONE);
-        } else {
-            Bitmap bmp = loadThumbnail(previewThumbnail, new Size(200, 200));
-            if (bmp == null) {
+            ImageView previewThumbnailView = findViewById(R.id.content_preview_thumbnail);
+            if (previewThumbnail == null) {
                 previewThumbnailView.setVisibility(View.GONE);
             } else {
-                previewThumbnailView.setImageBitmap(bmp);
+                Bitmap bmp = loadThumbnail(previewThumbnail, new Size(100, 100));
+                if (bmp == null) {
+                    previewThumbnailView.setVisibility(View.GONE);
+                } else {
+                    previewThumbnailView.setImageBitmap(bmp);
+                }
             }
         }
     }
@@ -2020,8 +2067,8 @@
         private void updatePath(int width, int height) {
             mPath.reset();
 
-            int imageWidth = width - getPaddingLeft() - getPaddingRight();
-            int imageHeight = height - getPaddingTop() - getPaddingBottom();
+            int imageWidth = width - getPaddingRight();
+            int imageHeight = height - getPaddingBottom();
             mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
                     mRadius, Path.Direction.CW);
         }
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 3f9ec45..79bfa17 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -15,6 +15,7 @@
 #include "Utils.h"
 #include "core_jni_helpers.h"
 
+#include <HardwareBitmapUploader.h>
 #include <nativehelper/JNIHelp.h>
 #include <androidfw/Asset.h>
 #include <androidfw/ResourceTypes.h>
@@ -278,6 +279,11 @@
 
     // Set the decode colorType
     SkColorType decodeColorType = codec->computeOutputColorType(prefColorType);
+    if (decodeColorType == kRGBA_F16_SkColorType && isHardware &&
+            !uirenderer::HardwareBitmapUploader::hasFP16Support()) {
+        decodeColorType = kN32_SkColorType;
+    }
+
     sk_sp<SkColorSpace> decodeColorSpace = codec->computeOutputColorSpace(
             decodeColorType, prefColorSpace);
 
@@ -354,13 +360,7 @@
     const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(),
             decodeColorType, alphaType, decodeColorSpace);
 
-    // For wide gamut images, we will leave the color space on the SkBitmap.  Otherwise,
-    // use the default.
     SkImageInfo bitmapInfo = decodeInfo;
-    if (decodeInfo.colorSpace() && decodeInfo.colorSpace()->isSRGB()) {
-        bitmapInfo = bitmapInfo.makeColorSpace(decodeInfo.refColorSpace());
-    }
-
     if (decodeColorType == kGray_8_SkColorType) {
         // The legacy implementation of BitmapFactory used kAlpha8 for
         // grayscale images (before kGray8 existed).  While the codec
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index b4ba749..d65f324 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -33,6 +33,7 @@
 #include "android_util_Binder.h"
 #include "core_jni_helpers.h"
 
+#include <HardwareBitmapUploader.h>
 #include <nativehelper/JNIHelp.h>
 #include <androidfw/Asset.h>
 #include <binder/Parcel.h>
@@ -166,6 +167,10 @@
 
     SkBitmapRegionDecoder* brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle);
     SkColorType decodeColorType = brd->computeOutputColorType(colorType);
+    if (decodeColorType == kRGBA_F16_SkColorType && isHardware &&
+            !uirenderer::HardwareBitmapUploader::hasFP16Support()) {
+        decodeColorType = kN32_SkColorType;
+    }
 
     // Set up the pixel allocator
     SkBRDAllocator* allocator = nullptr;
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index 72e14e7..2d83ac3 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -24,6 +24,7 @@
 #include "core_jni_helpers.h"
 
 #include <hwui/Bitmap.h>
+#include <HardwareBitmapUploader.h>
 
 #include <SkAndroidCodec.h>
 #include <SkEncodedImageFormat.h>
@@ -256,6 +257,17 @@
         // This is currently the only way to know that we should decode to F16.
         colorType = codec->computeOutputColorType(colorType);
     }
+
+    const bool isHardware = !requireMutable
+        && (allocator == ImageDecoder::kDefault_Allocator ||
+            allocator == ImageDecoder::kHardware_Allocator)
+        && colorType != kGray_8_SkColorType;
+
+    if (colorType == kRGBA_F16_SkColorType && isHardware &&
+            !uirenderer::HardwareBitmapUploader::hasFP16Support()) {
+        colorType = kN32_SkColorType;
+    }
+
     sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
     colorSpace = codec->computeOutputColorSpace(colorType, colorSpace);
     decodeInfo = decodeInfo.makeColorType(colorType).makeColorSpace(colorSpace);
@@ -449,10 +461,7 @@
     if (requireMutable) {
         bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Mutable;
     } else {
-        if ((allocator == ImageDecoder::kDefault_Allocator ||
-             allocator == ImageDecoder::kHardware_Allocator)
-            && bm.colorType() != kAlpha_8_SkColorType)
-        {
+        if (isHardware) {
             sk_sp<Bitmap> hwBitmap = Bitmap::allocateHardwareBitmap(bm);
             if (hwBitmap) {
                 hwBitmap->setImmutable();
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.cpp b/core/jni/android_hardware_input_InputApplicationHandle.cpp
index 1ab5843..b9301d4 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.cpp
+++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "InputApplicationHandle"
 
 #include <nativehelper/JNIHelp.h>
+#include "core_jni_helpers.h"
 #include "jni.h"
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/threads.h>
@@ -63,16 +64,7 @@
         mInfo = new InputApplicationInfo();
     }
 
-    jstring nameObj = jstring(env->GetObjectField(obj,
-            gInputApplicationHandleClassInfo.name));
-    if (nameObj) {
-        const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
-        mInfo->name = nameStr;
-        env->ReleaseStringUTFChars(nameObj, nameStr);
-        env->DeleteLocalRef(nameObj);
-    } else {
-        mInfo->name = "<null>";
-    }
+    mInfo->name = getStringField(env, obj, gInputApplicationHandleClassInfo.name, "<null>");
 
     mInfo->dispatchingTimeout = env->GetLongField(obj,
             gInputApplicationHandleClassInfo.dispatchingTimeoutNanos);
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 67a7441..eb71052 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "InputWindowHandle"
 
 #include <nativehelper/JNIHelp.h>
+#include "core_jni_helpers.h"
 #include "jni.h"
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/threads.h>
@@ -86,24 +87,14 @@
 
     mInfo.touchableRegion.clear();
 
-    jobject tokenObj = env->GetObjectField(obj,
-            gInputWindowHandleClassInfo.token);
+    jobject tokenObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.token);
     if (tokenObj) {
         mInfo.token = ibinderForJavaObject(env, tokenObj);
     } else {
         mInfo.token.clear();
     }
 
-    jstring nameObj = jstring(env->GetObjectField(obj,
-            gInputWindowHandleClassInfo.name));
-    if (nameObj) {
-        const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
-        mInfo.name = nameStr;
-        env->ReleaseStringUTFChars(nameObj, nameStr);
-        env->DeleteLocalRef(nameObj);
-    } else {
-        mInfo.name = "<null>";
-    }
+    mInfo.name = getStringField(env, obj, gInputWindowHandleClassInfo.name, "<null>");
 
     mInfo.layoutParamsFlags = env->GetIntField(obj,
             gInputWindowHandleClassInfo.layoutParamsFlags);
@@ -241,8 +232,7 @@
     GET_FIELD_ID(gInputWindowHandleClassInfo.ptr, clazz,
             "ptr", "J");
 
-    GET_FIELD_ID(gInputWindowHandleClassInfo.inputApplicationHandle,
-            clazz,
+    GET_FIELD_ID(gInputWindowHandleClassInfo.inputApplicationHandle, clazz,
             "inputApplicationHandle", "Landroid/view/InputApplicationHandle;");
 
     GET_FIELD_ID(gInputWindowHandleClassInfo.token, clazz,
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index fb6be6b..9819d9a 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "InputChannel-JNI"
 
 #include <nativehelper/JNIHelp.h>
-
+#include "nativehelper/scoped_utf_chars.h"
 #include <android_runtime/AndroidRuntime.h>
 #include <binder/Parcel.h>
 #include <utils/Log.h>
@@ -123,9 +123,8 @@
 
 static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
         jclass clazz, jstring nameObj) {
-    const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
-    std::string name = nameChars;
-    env->ReleaseStringUTFChars(nameObj, nameChars);
+    ScopedUtfChars nameChars(env, nameObj);
+    std::string name = nameChars.c_str();
 
     sp<InputChannel> serverChannel;
     sp<InputChannel> clientChannel;
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 0d75de9..ce5512b 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -507,15 +507,15 @@
 jmethodID gPositionListener_PositionLostMethod;
 
 static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
-        jlong renderNodePtr, jobject surfaceview) {
-    class SurfaceViewPositionUpdater : public RenderNode::PositionListener {
+        jlong renderNodePtr, jobject listener) {
+    class PositionListenerTrampoline : public RenderNode::PositionListener {
     public:
-        SurfaceViewPositionUpdater(JNIEnv* env, jobject surfaceview) {
+        PositionListenerTrampoline(JNIEnv* env, jobject listener) {
             env->GetJavaVM(&mVm);
-            mWeakRef = env->NewWeakGlobalRef(surfaceview);
+            mWeakRef = env->NewWeakGlobalRef(listener);
         }
 
-        virtual ~SurfaceViewPositionUpdater() {
+        virtual ~PositionListenerTrampoline() {
             jnienv()->DeleteWeakGlobalRef(mWeakRef);
             mWeakRef = nullptr;
         }
@@ -539,9 +539,14 @@
                 bounds.roundOut();
             }
 
+            if (mPreviousPosition == bounds) {
+                return;
+            }
+            mPreviousPosition = bounds;
+
             incStrong(0);
             auto functor = std::bind(
-                std::mem_fn(&SurfaceViewPositionUpdater::doUpdatePositionAsync), this,
+                std::mem_fn(&PositionListenerTrampoline::doUpdatePositionAsync), this,
                 (jlong) info.canvasContext.getFrameNumber(),
                 (jint) bounds.left, (jint) bounds.top,
                 (jint) bounds.right, (jint) bounds.bottom);
@@ -552,6 +557,11 @@
         virtual void onPositionLost(RenderNode& node, const TreeInfo* info) override {
             if (CC_UNLIKELY(!mWeakRef || (info && !info->updateWindowPositions))) return;
 
+            if (mPreviousPosition.isEmpty()) {
+                return;
+            }
+            mPreviousPosition.setEmpty();
+
             ATRACE_NAME("SurfaceView position lost");
             JNIEnv* env = jnienv();
             jobject localref = env->NewLocalRef(mWeakRef);
@@ -561,6 +571,7 @@
                 return;
             }
 
+            // TODO: Remember why this is synchronous and then make a comment
             env->CallVoidMethod(localref, gPositionListener_PositionLostMethod,
                     info ? info->canvasContext.getFrameNumber() : 0);
             env->DeleteLocalRef(localref);
@@ -596,10 +607,11 @@
 
         JavaVM* mVm;
         jobject mWeakRef;
+        uirenderer::Rect mPreviousPosition;
     };
 
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->setPositionListener(new SurfaceViewPositionUpdater(env, surfaceview));
+    renderNode->setPositionListener(new PositionListenerTrampoline(env, listener));
 }
 
 // ----------------------------------------------------------------------------
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index f1b259e..fad2fe0 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -36,6 +36,7 @@
 #include <memory>
 #include <stdio.h>
 #include <system/graphics.h>
+#include <ui/ConfigStoreTypes.h>
 #include <ui/DisplayInfo.h>
 #include <ui/DisplayedFrameStats.h>
 #include <ui/FrameStats.h>
@@ -108,6 +109,23 @@
     jmethodID ctor;
 } gDisplayedContentSamplingAttributesClassInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+    jfieldID X;
+    jfieldID Y;
+    jfieldID Z;
+} gCieXyzClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+    jfieldID red;
+    jfieldID green;
+    jfieldID blue;
+    jfieldID white;
+} gDisplayPrimariesClassInfo;
+
 // ----------------------------------------------------------------------------
 
 static jlong nativeCreateTransaction(JNIEnv* env, jclass clazz) {
@@ -688,6 +706,66 @@
     return colorModesArray;
 }
 
+static jobject nativeGetDisplayNativePrimaries(JNIEnv* env, jclass, jobject tokenObj) {
+    sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+    if (token == NULL) return NULL;
+
+    ui::DisplayPrimaries primaries;
+    if (SurfaceComposerClient::getDisplayNativePrimaries(token, primaries) != NO_ERROR) {
+        return NULL;
+    }
+
+    jobject jred = env->NewObject(gCieXyzClassInfo.clazz, gCieXyzClassInfo.ctor);
+    if (jred == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return NULL;
+    }
+
+    jobject jgreen = env->NewObject(gCieXyzClassInfo.clazz, gCieXyzClassInfo.ctor);
+    if (jgreen == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return NULL;
+    }
+
+    jobject jblue = env->NewObject(gCieXyzClassInfo.clazz, gCieXyzClassInfo.ctor);
+    if (jblue == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return NULL;
+    }
+
+    jobject jwhite = env->NewObject(gCieXyzClassInfo.clazz, gCieXyzClassInfo.ctor);
+    if (jwhite == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return NULL;
+    }
+
+    jobject jprimaries = env->NewObject(gDisplayPrimariesClassInfo.clazz,
+            gDisplayPrimariesClassInfo.ctor);
+    if (jprimaries == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return NULL;
+    }
+
+    env->SetFloatField(jred, gCieXyzClassInfo.X, primaries.red.X);
+    env->SetFloatField(jred, gCieXyzClassInfo.Y, primaries.red.Y);
+    env->SetFloatField(jred, gCieXyzClassInfo.Z, primaries.red.Z);
+    env->SetFloatField(jgreen, gCieXyzClassInfo.X, primaries.green.X);
+    env->SetFloatField(jgreen, gCieXyzClassInfo.Y, primaries.green.Y);
+    env->SetFloatField(jgreen, gCieXyzClassInfo.Z, primaries.green.Z);
+    env->SetFloatField(jblue, gCieXyzClassInfo.X, primaries.blue.X);
+    env->SetFloatField(jblue, gCieXyzClassInfo.Y, primaries.blue.Y);
+    env->SetFloatField(jblue, gCieXyzClassInfo.Z, primaries.blue.Z);
+    env->SetFloatField(jwhite, gCieXyzClassInfo.X, primaries.white.X);
+    env->SetFloatField(jwhite, gCieXyzClassInfo.Y, primaries.white.Y);
+    env->SetFloatField(jwhite, gCieXyzClassInfo.Z, primaries.white.Z);
+    env->SetObjectField(jprimaries, gDisplayPrimariesClassInfo.red, jred);
+    env->SetObjectField(jprimaries, gDisplayPrimariesClassInfo.green, jgreen);
+    env->SetObjectField(jprimaries, gDisplayPrimariesClassInfo.blue, jblue);
+    env->SetObjectField(jprimaries, gDisplayPrimariesClassInfo.white, jwhite);
+
+    return jprimaries;
+}
+
 static jint nativeGetActiveColorMode(JNIEnv* env, jclass, jobject tokenObj) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == NULL) return -1;
@@ -1089,6 +1167,8 @@
             (void*)nativeSetActiveConfig },
     {"nativeGetDisplayColorModes", "(Landroid/os/IBinder;)[I",
             (void*)nativeGetDisplayColorModes},
+    {"nativeGetDisplayNativePrimaries", "(Landroid/os/IBinder;)Landroid/view/SurfaceControl$DisplayPrimaries;",
+            (void*)nativeGetDisplayNativePrimaries },
     {"nativeGetActiveColorMode", "(Landroid/os/IBinder;)I",
             (void*)nativeGetActiveColorMode},
     {"nativeSetActiveColorMode", "(Landroid/os/IBinder;I)Z",
@@ -1209,6 +1289,28 @@
             displayedContentSamplingAttributesClazz);
     gDisplayedContentSamplingAttributesClassInfo.ctor = GetMethodIDOrDie(env,
             displayedContentSamplingAttributesClazz, "<init>", "(III)V");
+
+    jclass cieXyzClazz = FindClassOrDie(env, "android/view/SurfaceControl$CieXyz");
+    gCieXyzClassInfo.clazz = MakeGlobalRefOrDie(env, cieXyzClazz);
+    gCieXyzClassInfo.ctor = GetMethodIDOrDie(env, gCieXyzClassInfo.clazz, "<init>", "()V");
+    gCieXyzClassInfo.X = GetFieldIDOrDie(env, cieXyzClazz, "X", "F");
+    gCieXyzClassInfo.Y = GetFieldIDOrDie(env, cieXyzClazz, "Y", "F");
+    gCieXyzClassInfo.Z = GetFieldIDOrDie(env, cieXyzClazz, "Z", "F");
+
+    jclass displayPrimariesClazz = FindClassOrDie(env,
+            "android/view/SurfaceControl$DisplayPrimaries");
+    gDisplayPrimariesClassInfo.clazz = MakeGlobalRefOrDie(env, displayPrimariesClazz);
+    gDisplayPrimariesClassInfo.ctor = GetMethodIDOrDie(env, gDisplayPrimariesClassInfo.clazz,
+            "<init>", "()V");
+    gDisplayPrimariesClassInfo.red = GetFieldIDOrDie(env, displayPrimariesClazz, "red",
+            "Landroid/view/SurfaceControl$CieXyz;");
+    gDisplayPrimariesClassInfo.green = GetFieldIDOrDie(env, displayPrimariesClazz, "green",
+            "Landroid/view/SurfaceControl$CieXyz;");
+    gDisplayPrimariesClassInfo.blue = GetFieldIDOrDie(env, displayPrimariesClazz, "blue",
+            "Landroid/view/SurfaceControl$CieXyz;");
+    gDisplayPrimariesClassInfo.white = GetFieldIDOrDie(env, displayPrimariesClazz, "white",
+            "Landroid/view/SurfaceControl$CieXyz;");
+
     return err;
 }
 
diff --git a/core/jni/core_jni_helpers.h b/core/jni/core_jni_helpers.h
index 1325b0c..16ef753 100644
--- a/core/jni/core_jni_helpers.h
+++ b/core/jni/core_jni_helpers.h
@@ -18,6 +18,8 @@
 #define CORE_JNI_HELPERS
 
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/scoped_local_ref.h>
+#include <nativehelper/scoped_utf_chars.h>
 #include <android_runtime/AndroidRuntime.h>
 
 namespace android {
@@ -72,6 +74,20 @@
     return res;
 }
 
+/**
+ * Read the specified field from jobject, and convert to std::string.
+ * If the field cannot be obtained, return defaultValue.
+ */
+static inline std::string getStringField(JNIEnv* env, jobject obj, jfieldID fieldId,
+        const char* defaultValue) {
+    ScopedLocalRef<jstring> strObj(env, jstring(env->GetObjectField(obj, fieldId)));
+    if (strObj != nullptr) {
+        ScopedUtfChars chars(env, strObj.get());
+        return std::string(chars.c_str());
+    }
+    return std::string(defaultValue);
+}
+
 }  // namespace android
 
 #endif  // CORE_JNI_HELPERS
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index f2f7304..631fa1d 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -595,6 +595,11 @@
 
     // ACTION: Tap & Pay -> Default Application Setting -> Use Default
     ACTION_NFC_PAYMENT_ALWAYS_SETTING = 1623;
+
+    // ACTION: Settings > Search Bar > Avatar
+    // CATEGORY: SETTINGS
+    // OS: Q
+    CLICK_ACCOUNT_AVATAR = 1643;
 }
 
 /**
diff --git a/core/res/res/drawable/chooser_content_preview_rounded.xml b/core/res/res/drawable/chooser_content_preview_rounded.xml
new file mode 100644
index 0000000..7d85738
--- /dev/null
+++ b/core/res/res/drawable/chooser_content_preview_rounded.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <stroke
+        android:width="1dp"
+        android:color="#ccc" />
+ 
+    <corners android:radius="@dimen/chooser_corner_radius" />
+
+    <padding
+        android:left="16dp"
+        android:top="12dp"
+        android:right="16dp"
+        android:bottom="12dp"/>
+</shape>
diff --git a/core/res/res/drawable/ic_content_copy_gm2.xml b/core/res/res/drawable/ic_content_copy_gm2.xml
new file mode 100644
index 0000000..940da94
--- /dev/null
+++ b/core/res/res/drawable/ic_content_copy_gm2.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#F999"
+      android:pathData="M18,21L4,21L4,7L2,7v14c0,1.1 0.9,2 2,2h14v-2zM21,17L21,3c0,-1.1 -0.9,-2 -2,-2L8,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2zM19,17L8,17L8,3h11v14z"/>
+</vector>
diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml
index c42f43a..a24f920 100644
--- a/core/res/res/layout/chooser_grid.xml
+++ b/core/res/res/layout/chooser_grid.xml
@@ -17,19 +17,19 @@
 */
 -->
 <com.android.internal.widget.ResolverDrawerLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:maxWidth="@dimen/resolver_max_width"
-        android:maxCollapsedHeight="288dp"
-        android:maxCollapsedHeightSmall="56dp"
-        android:id="@id/contentPanel">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:maxWidth="@dimen/resolver_max_width"
+    android:maxCollapsedHeight="288dp"
+    android:maxCollapsedHeightSmall="56dp"
+    android:id="@id/contentPanel">
 
     <RelativeLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alwaysShow="true"
-            android:background="?attr/colorBackgroundFloating" >
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alwaysShow="true"
+        android:background="?attr/colorBackgroundFloating">
         <TextView android:id="@+id/profile_button"
                   android:layout_width="wrap_content"
                   android:layout_height="48dp"
@@ -51,73 +51,98 @@
                   android:textAppearance="?attr/textAppearanceMedium"
                   android:textSize="20sp"
                   android:gravity="center"
-                  android:paddingEnd="?attr/dialogPreferredPadding"
-                  android:paddingTop="12dp"
-                  android:paddingBottom="6dp"
+                  android:paddingTop="18dp"
+                  android:paddingBottom="18dp"
+                  android:paddingLeft="24dp"
+                  android:paddingRight="24dp"
                   android:layout_below="@id/profile_button"
                   android:layout_centerHorizontal="true"/>
     </RelativeLayout>
 
-    <RelativeLayout 
+    <LinearLayout
         android:id="@+id/content_preview"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingBottom="@dimen/chooser_view_spacing"
         android:background="?attr/colorBackgroundFloating">
-      
-      <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView"
-            android:id="@+id/content_preview_thumbnail"
-            android:layout_alignParentTop="true"
-            android:layout_width="100dp"
+
+        <LinearLayout
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_alignParentStart="true"
-            android:gravity="center"
-            android:adjustViewBounds="true"
-            android:maxWidth="90dp"
-            android:maxHeight="90dp"
-            android:scaleType="fitCenter"
-            android:padding="5dp"/>
+            android:orientation="horizontal"
+            android:paddingLeft="@dimen/chooser_edge_margin_normal"
+            android:paddingRight="@dimen/chooser_edge_margin_normal"
+            android:layout_marginBottom="@dimen/chooser_view_spacing"
+            android:id="@+id/content_preview_text_layout">
+            <TextView
+                android:id="@+id/content_preview_text"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:ellipsize="end"
+                android:gravity="start|top"
+                android:paddingRight="24dp"
+                android:maxLines="2"/>
+            <Button
+                android:id="@+id/copy_button"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:gravity="center"
+                android:layout_gravity="center_vertical"
+                android:background="@drawable/ic_content_copy_gm2"/>
+        </LinearLayout>
 
-      <TextView
-          android:id="@+id/content_preview_title"
-          android:layout_alignParentTop="true"
-          android:layout_toEndOf="@id/content_preview_thumbnail"
-          android:layout_width="match_parent"
-          android:layout_height="wrap_content"
-          android:gravity="start|top"
-          android:textAppearance="?attr/textAppearanceMedium"
-          android:maxLines="2"
-          android:ellipsize="end"
-          android:paddingStart="15dp"
-          android:paddingEnd="15dp"
-          android:paddingTop="10dp" />
+        <!-- Required sub-layout so we can get the nice rounded corners-->
+        <!-- around this section -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_marginLeft="@dimen/chooser_edge_margin_thin"
+            android:layout_marginRight="@dimen/chooser_edge_margin_thin"
+            android:minHeight="80dp"
+            android:background="@drawable/chooser_content_preview_rounded"
+            android:id="@+id/content_preview_title_layout">
 
-      <TextView
-          android:id="@+id/content_preview_text"
-          android:layout_width="match_parent"
-          android:layout_height="wrap_content"
-          android:layout_below="@id/content_preview_title"
-          android:layout_toEndOf="@id/content_preview_thumbnail"
-          android:gravity="start|top"
-          android:maxLines="2"
-          android:ellipsize="end"
-          android:paddingStart="15dp"
-          android:paddingEnd="15dp"
-          android:paddingTop="10dp"
-          android:paddingBottom="5dp"/>
-    </RelativeLayout>
+            <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView"
+                  android:id="@+id/content_preview_thumbnail"
+                  android:layout_width="80dp"
+                  android:layout_height="80dp"
+                  android:layout_marginRight="12dp"
+                  android:adjustViewBounds="true"
+                  android:layout_gravity="center_vertical"
+                  android:gravity="center"
+                  android:maxWidth="70dp"
+                  android:maxHeight="70dp"
+                  android:padding="5dp"
+                  android:scaleType="centerCrop"/>
+
+            <TextView
+                android:id="@+id/content_preview_title"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:ellipsize="end"
+                android:maxLines="2"
+                android:textAppearance="?attr/textAppearanceMedium"/>
+        </LinearLayout>
+    </LinearLayout>
 
     <ListView
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:id="@+id/resolver_list"
-            android:clipToPadding="false"
-            android:scrollbarStyle="outsideOverlay"
-            android:background="?attr/colorBackgroundFloating"
-            android:elevation="8dp"
-            android:listSelector="@color/transparent"
-            android:divider="@null"
-            android:scrollIndicators="top"
-            android:nestedScrollingEnabled="true" />
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/resolver_list"
+        android:clipToPadding="false"
+        android:scrollbarStyle="outsideOverlay"
+        android:background="?attr/colorBackgroundFloating"
+        android:elevation="8dp"
+        android:listSelector="@color/transparent"
+        android:divider="@null"
+        android:scrollIndicators="top"
+        android:nestedScrollingEnabled="true"/>
 
     <TextView android:id="@+id/empty"
               android:layout_width="match_parent"
@@ -127,6 +152,6 @@
               android:text="@string/noApplications"
               android:padding="32dp"
               android:gravity="center"
-              android:visibility="gone" />
+              android:visibility="gone"/>
 
 </com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 1eece03..5fe1d4e 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1138,10 +1138,12 @@
          resource] to be present in order to function. Default value is false. -->
     <attr name="isSplitRequired" format="boolean" />
 
-    <!-- Flag to specify if this app prioritizes code integrity. The system may choose
-         to run with better integrity guarantee in various components if possible based on the app's
-         <code>targetSdkVersion</code>. -->
-    <attr name="preferCodeIntegrity" format="boolean" />
+    <!-- Flag to specify if this app wants to run the dex within its APK but not extracted or
+         locally compiled variants. This keeps the dex code protected by the APK signature. Such
+         apps will always run in JIT mode (same when they are first installed), and the system will
+         never generate ahead-of-time compiled code for them. Depending on the app's workload,
+         there may be some run time performance change, noteably the cold start time. -->
+    <attr name="useEmbeddedDex" format="boolean" />
 
     <!-- Extra options for an activity's UI. Applies to either the {@code <activity>} or
          {@code <application>} tag. If specified on the {@code <application>}
@@ -1611,7 +1613,7 @@
              to honor this flag as well. -->
         <attr name="usesCleartextTraffic" />
         <attr name="multiArch" />
-        <attr name="preferCodeIntegrity" />
+        <attr name="useEmbeddedDex" />
         <attr name="extractNativeLibs" />
         <attr name="defaultToDeviceProtectedStorage" format="boolean" />
         <attr name="directBootAware" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index eb214ba..4ad2072 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3781,4 +3781,105 @@
 
     <!-- Whether or not aware is enabled by default -->
     <bool name="config_awareSettingAvailable">false</bool>
+
+    <!-- Display White-Balance -->
+
+    <!-- See AmbientSensor.AmbientBrightnessSensor.
+         The ambient brightness sensor rate (in milliseconds). Must be positive. -->
+    <integer name="config_displayWhiteBalanceBrightnessSensorRate">250</integer>
+
+    <!-- See AmbientFilter.
+         How long ambient brightness changes are kept and taken into consideration
+         (in milliseconds). Must be positive. -->
+    <integer name="config_displayWhiteBalanceBrightnessFilterHorizon">10000</integer>
+
+    <!-- See AmbientFilter.WeightedMovingAverageAmbientFilter.
+         Recent changes are prioritised by integrating their duration over y = x + intercept
+         (the higher it is, the less prioritised recent changes are). Must be a non-negative
+         number, or NaN to avoid this implementation. -->
+    <item name="config_displayWhiteBalanceBrightnessFilterIntercept" format="float" type="dimen">10.0</item>
+
+    <!-- See AmbientSensor.AmbientColorTemperatureSensor.
+         The ambient color temperature sensor name. -->
+    <string name="config_displayWhiteBalanceColorTemperatureSensorName">com.google.sensor.color</string>
+
+    <!-- See AmbientSensor.AmbientColorTemperatureSensor.
+         The ambient color temperature sensor rate (in milliseconds). Must be positive. -->
+    <integer name="config_displayWhiteBalanceColorTemperatureSensorRate">250</integer>
+
+    <!-- See AmbientFilter.
+         How long ambient color temperature changes are kept and taken into consideration
+         (in milliseconds). Must be positive. -->
+    <integer name="config_displayWhiteBalanceColorTemperatureFilterHorizon">10000</integer>
+
+    <!-- See AmbientFilter.WeightedMovingAverageAmbientFilter.
+         Recent changes are prioritised by integrating their duration over y = x + intercept
+         (the higher it is, the less prioritised recent changes are). Must be a non-negative
+         number, or NaN to avoid this implementation. -->
+    <item name="config_displayWhiteBalanceColorTemperatureFilterIntercept" format="float"
+            type="dimen">10.0</item>
+
+    <!-- See DisplayWhiteBalanceThrottler.
+         The debounce time (in milliseconds) for increasing the screen color temperature, throttled
+         if time > lastTime + debounce. Must be non-negative. -->
+    <integer name="config_displayWhiteBalanceIncreaseDebounce">5000</integer>
+
+    <!-- See DisplayWhiteBalanceThrottler.
+         The debounce time (in milliseconds) for decreasing the screen color tempearture, throttled
+         if time < lastTime - debounce. Must be non-negative. -->
+    <integer name="config_displayWhiteBalanceDecreaseDebounce">5000</integer>
+
+    <!-- See DisplayWhiteBalanceThrottler.
+         The ambient color temperature values used to determine the threshold as the corresponding
+         value in config_displayWhiteBalance{Increase,Decrease}Threholds. Must be non-empty, the
+         same length as config_displayWhiteBalance{Increase,Decrease}Thresholds, and contain
+         non-negative, strictly increasing numbers.
+
+         For example, if:
+
+         - baseThresolds = [0, 100, 1000];
+         - increaseThresholds = [0.1, 0.15, 0.2];
+         - decreaseThresholds = [0.1, 0.05, 0.0];
+
+         Then, given the ambient color temperature INCREASED from X to Y (so X < Y):
+         - If 0 <= Y < 100, we require Y > (1 + 0.1) * X = 1.1X;
+         - If 100 <= Y < 1000, we require Y > (1 + 0.15) * X = 1.15X;
+         - If 1000 <= Y, we require Y > (1 + 0.2) * X = 1.2X.
+
+         Or, if the ambient color temperature DECREASED from X to Y (so X > Y):
+         - If 0 <= Y < 100, we require Y < (1 - 0.1) * X = 0.9X;
+         - If 100 <= Y < 1000, we require Y < (1 - 0.05) * X = 0.95X;
+         - If 1000 <= Y, we require Y < (1 - 0) * X = X.
+
+         NOTE: the numbers in this example are made up, and don't represent how actual base,
+               increase or decrease thresholds would look like. -->
+    <array name="config_displayWhiteBalanceBaseThresholds">
+        <item>0.0</item>
+    </array>
+
+    <!-- See DisplayWhiteBalanceThrottler.
+         The increase threshold values, throttled if value < value * (1 + threshold). Must be
+         non-empty, the same length as config_displayWhiteBalanceBaseThresholds, and contain
+         non-negative numbers. -->
+    <array name="config_displayWhiteBalanceIncreaseThresholds">
+        <item>0.1</item>
+    </array>
+
+    <!-- See DisplayWhiteBalanceThrottler.
+         The decrease threshold values, throttled if value > value * (1 - threshold). Must be
+         non-empty, the same length as config_displayWhiteBalanceBaseThresholds, and contain
+         non-negative numbers. -->
+    <array name="config_displayWhiteBalanceDecreaseThresholds">
+        <item>0.1</item>
+    </array>
+
+    <!-- See DisplayWhiteBalanceController.
+         The ambient brightness threshold (in lux) beneath which we fall back to a fixed ambient
+         color temperature. -->
+    <item name="config_displayWhiteBalanceLowLightAmbientBrightnessThreshold" format="float" type="dimen">10.0</item>
+
+    <!-- See DisplayWhiteBalanceController.
+         The ambient color temperature (in cct) to which we fall back when the ambient brightness
+         drops beneath a certain threshold. -->
+    <item name="config_displayWhiteBalanceLowLightAmbientColorTemperature" format="float" type="dimen">6500.0</item>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index baf7587..38367fb 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -715,6 +715,9 @@
     <!-- Line spacing modifier for the message field of the harmful app dialog -->
     <item name="harmful_app_message_line_spacing_modifier" type="dimen">1.22</item>
 
-    <!-- chooser corner radius -->
-    <dimen name="chooser_corner_radius">4dp</dimen>
+    <!-- chooser (sharesheet) spacing -->
+    <dimen name="chooser_corner_radius">8dp</dimen>
+    <dimen name="chooser_view_spacing">18dp</dimen>
+    <dimen name="chooser_edge_margin_thin">16dp</dimen>
+    <dimen name="chooser_edge_margin_normal">24dp</dimen>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 04df97c..fadb28f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2645,6 +2645,9 @@
     <!-- Displayed to the user to confirm that they have copied text from a web page to the clipboard. -->
     <string name="text_copied">Text copied to clipboard.</string>
 
+    <!-- Displayed to the user to confirm that they have copied text/images to the clipboard [CHAR LIMIT=NONE] -->
+    <string name="copied">Copied</string>
+
     <!-- Menu item displayed at the end of a menu to allow users to see another page worth of menu items. This is shown on any app's menu as long as the app has too many items in the menu.-->
     <string name="more_item_label">More</string>
     <!-- Prepended to the shortcut for a menu item to indicate that the user should hold the MENU button together with the shortcut to invoke the item. For example, if the shortcut to open a new tab in browser is MENU and B together, then this would be prepended to the letter "B" -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 52400de..ca8fa82 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -53,7 +53,10 @@
   <java-symbol type="id" name="content_preview" />
   <java-symbol type="id" name="content_preview_thumbnail" />
   <java-symbol type="id" name="content_preview_text" />
+  <java-symbol type="id" name="content_preview_text_layout" />
   <java-symbol type="id" name="content_preview_title" />
+  <java-symbol type="id" name="content_preview_title_layout" />
+  <java-symbol type="id" name="copy_button" />
   <java-symbol type="id" name="current_scene" />
   <java-symbol type="id" name="scene_layoutid_cache" />
   <java-symbol type="id" name="customPanel" />
@@ -545,6 +548,7 @@
   <java-symbol type="string" name="activity_resolver_work_profiles_support" />
   <java-symbol type="string" name="app_running_notification_title" />
   <java-symbol type="string" name="app_running_notification_text" />
+  <java-symbol type="string" name="copied" />
   <java-symbol type="string" name="delete" />
   <java-symbol type="string" name="deleteText" />
   <java-symbol type="string" name="grant_permissions_header_text" />
@@ -2711,6 +2715,9 @@
   <java-symbol type="id" name="month_view" />
   <java-symbol type="integer" name="config_zen_repeat_callers_threshold" />
   <java-symbol type="dimen" name="chooser_corner_radius" />
+  <java-symbol type="dimen" name="chooser_view_spacing" />
+  <java-symbol type="dimen" name="chooser_edge_margin_thin" />
+  <java-symbol type="dimen" name="chooser_edge_margin_normal" />
   <java-symbol type="layout" name="chooser_grid" />
   <java-symbol type="layout" name="resolve_grid_item" />
   <java-symbol type="id" name="day_picker_view_pager" />
@@ -3591,4 +3598,20 @@
   <java-symbol type="integer" name="config_attentionApiTimeout" />
 
   <java-symbol type="string" name="config_incidentReportApproverPackage" />
+
+  <!-- Display White-Balance -->
+  <java-symbol type="integer" name="config_displayWhiteBalanceBrightnessSensorRate" />
+  <java-symbol type="integer" name="config_displayWhiteBalanceBrightnessFilterHorizon" />
+  <java-symbol type="dimen" name="config_displayWhiteBalanceBrightnessFilterIntercept" />
+  <java-symbol type="string" name="config_displayWhiteBalanceColorTemperatureSensorName" />
+  <java-symbol type="integer" name="config_displayWhiteBalanceColorTemperatureSensorRate" />
+  <java-symbol type="integer" name="config_displayWhiteBalanceColorTemperatureFilterHorizon" />
+  <java-symbol type="dimen" name="config_displayWhiteBalanceColorTemperatureFilterIntercept" />
+  <java-symbol type="integer" name="config_displayWhiteBalanceIncreaseDebounce" />
+  <java-symbol type="integer" name="config_displayWhiteBalanceDecreaseDebounce" />
+  <java-symbol type="array" name="config_displayWhiteBalanceBaseThresholds" />
+  <java-symbol type="array" name="config_displayWhiteBalanceIncreaseThresholds" />
+  <java-symbol type="array" name="config_displayWhiteBalanceDecreaseThresholds" />
+  <java-symbol type="dimen" name="config_displayWhiteBalanceLowLightAmbientBrightnessThreshold" />
+  <java-symbol type="dimen" name="config_displayWhiteBalanceLowLightAmbientColorTemperature" />
 </resources>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 268bb81..1764249 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -59,6 +59,7 @@
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.READ_DREAM_STATE" />
     <uses-permission android:name="android.permission.WRITE_DREAM_STATE" />
+    <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
     <uses-permission android:name="android.permission.READ_LOGS"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.READ_SMS"/>
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 21fcae7..6b9e69c 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -34,6 +34,9 @@
 
 import android.app.usage.UsageStatsManager;
 import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ClipboardManager;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
@@ -392,6 +395,32 @@
         assertThat(chosen[0], is(toChoose));
     }
 
+    @Test
+    public void copyTextToClipboard() throws Exception {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(1);
+
+        when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(R.id.copy_button)).perform(click());
+
+        ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(
+                Context.CLIPBOARD_SERVICE);
+        ClipData clipData = clipboard.getPrimaryClip();
+        assertThat("testing intent sending", is(clipData.getItemAt(0).getText()));
+
+        ClipDescription clipDescription = clipData.getDescription();
+        assertThat("text/plain", is(clipDescription.getMimeType(0)));
+    }
+
     private Intent createSendTextIntent() {
         Intent sendIntent = new Intent();
         sendIntent.setAction(Intent.ACTION_SEND);
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 39bfcdd..6b7ec97 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -86,7 +86,7 @@
     return sEglManager.eglDisplay();
 }
 
-static bool hasFP16Support() {
+bool HardwareBitmapUploader::hasFP16Support() {
     static std::once_flag sOnce;
     static bool hasFP16Support = false;
 
@@ -127,7 +127,7 @@
             formatInfo.type = GL_UNSIGNED_BYTE;
             break;
         case kRGBA_F16_SkColorType:
-            formatInfo.isSupported = hasFP16Support();
+            formatInfo.isSupported = HardwareBitmapUploader::hasFP16Support();
             if (formatInfo.isSupported) {
                 formatInfo.type = GL_HALF_FLOAT;
                 formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_FP16;
diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h
index 40f2b0c..6f41e6d 100644
--- a/libs/hwui/HardwareBitmapUploader.h
+++ b/libs/hwui/HardwareBitmapUploader.h
@@ -20,10 +20,12 @@
 
 namespace android::uirenderer {
 
-class HardwareBitmapUploader {
+class ANDROID_API HardwareBitmapUploader {
 public:
     static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& sourceBitmap);
     static void terminate();
+
+    static bool hasFP16Support();
 };
 
 }  // namespace android::uirenderer
diff --git a/media/Android.bp b/media/Android.bp
index d7b8dd2..c7d5ace 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -75,7 +75,6 @@
         "apex/java/android/media/UriDataSourceDesc.java",
         "apex/java/android/media/FileDataSourceDesc.java",
         "apex/java/android/media/CallbackDataSourceDesc.java",
-        "apex/java/android/media/VideoSize.java",
         "apex/java/android/media/Media2Utils.java",
         "apex/java/android/media/MediaPlayer2Utils.java",
         "apex/java/android/media/MediaPlayer2.java",
diff --git a/media/apex/java/android/media/MediaPlayer2.java b/media/apex/java/android/media/MediaPlayer2.java
index 0fd496b..925ca0d 100644
--- a/media/apex/java/android/media/MediaPlayer2.java
+++ b/media/apex/java/android/media/MediaPlayer2.java
@@ -41,6 +41,7 @@
 import android.os.PowerManager;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Size;
 import android.view.Surface;
 import android.view.SurfaceHolder;
 
@@ -62,7 +63,6 @@
 import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -300,7 +300,7 @@
     private final AtomicLong mSrcIdGenerator = new AtomicLong(0);
 
     private volatile float mVolume = 1.0f;
-    private VideoSize mVideoSize = new VideoSize(0, 0);
+    private Size mVideoSize = new Size(0, 0);
 
     private static ExecutorService sDrmThreadPool = Executors.newCachedThreadPool();
 
@@ -1527,7 +1527,7 @@
      * notification {@code EventCallback.onVideoSizeChanged} when the size
      * is available.
      */
-    public VideoSize getVideoSize() {
+    public Size getVideoSize() {
         return mVideoSize;
     }
 
@@ -2527,7 +2527,7 @@
                     final int width = msg.arg1;
                     final int height = msg.arg2;
 
-                    mVideoSize = new VideoSize(width, height);
+                    mVideoSize = new Size(width, height);
                     sendEvent(new EventNotifier() {
                         @Override
                         public void notify(EventCallback callback) {
@@ -2766,7 +2766,7 @@
          * @param size the size of the video
          */
         public void onVideoSizeChanged(
-                @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, @NonNull VideoSize size) { }
+                @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, @NonNull Size size) { }
 
         /**
          * Called to indicate an avaliable timed text
diff --git a/media/apex/java/android/media/VideoSize.java b/media/apex/java/android/media/VideoSize.java
deleted file mode 100644
index 19631e09..0000000
--- a/media/apex/java/android/media/VideoSize.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2018 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.media;
-
-/**
- * Immutable class for describing width and height dimensions.
- */
-public final class VideoSize {
-    /**
-     * Create a new immutable VideoSize instance.
-     *
-     * @param width The width of the video size
-     * @param height The height of the video size
-     */
-    VideoSize(int width, int height) {
-        mWidth = width;
-        mHeight = height;
-    }
-
-    /**
-     * Get the width of the video size
-     * @return width
-     */
-    public int getWidth() {
-        return mWidth;
-    }
-
-    /**
-     * Get the height of the video size
-     * @return height
-     */
-    public int getHeight() {
-        return mHeight;
-    }
-
-    /**
-     * Check if this video size is equal to another video size.
-     * <p>
-     * Two video sizes are equal if and only if both their widths and heights are
-     * equal.
-     * </p>
-     * <p>
-     * A video size object is never equal to any other type of object.
-     * </p>
-     *
-     * @return {@code true} if the objects were equal, {@code false} otherwise
-     */
-    @Override
-    public boolean equals(final Object obj) {
-        if (obj == null) {
-            return false;
-        }
-        if (this == obj) {
-            return true;
-        }
-        if (obj instanceof VideoSize) {
-            VideoSize other = (VideoSize) obj;
-            return mWidth == other.mWidth && mHeight == other.mHeight;
-        }
-        return false;
-    }
-
-    /**
-     * Return the video size represented as a string with the format {@code "WxH"}
-     *
-     * @return string representation of the video size
-     */
-    @Override
-    public String toString() {
-        return mWidth + "x" + mHeight;
-    }
-
-    private final int mWidth;
-    private final int mHeight;
-}
diff --git a/packages/SystemUI/res/drawable/ic_open_in_new.xml b/packages/SystemUI/res/drawable/ic_open_in_new.xml
new file mode 100644
index 0000000..3d53871
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_open_in_new.xml
@@ -0,0 +1,23 @@
+<!-- Copyright (C) 2019 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/bubble_expanded_view.xml b/packages/SystemUI/res/layout/bubble_expanded_view.xml
index 1aeb52c..f0d2b2e 100644
--- a/packages/SystemUI/res/layout/bubble_expanded_view.xml
+++ b/packages/SystemUI/res/layout/bubble_expanded_view.xml
@@ -18,6 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
+    android:orientation="vertical"
     android:id="@+id/bubble_expanded_view">
 
     <View
@@ -26,17 +27,48 @@
         android:layout_height="@dimen/bubble_pointer_height"
     />
 
-    <TextView
-        android:id="@+id/bubble_content_header"
-        android:background="@drawable/bubble_expanded_header_bg"
-        android:textAppearance="@*android:style/TextAppearance.Material.Title"
-        android:textSize="18sp"
-        android:layout_width="match_parent"
+    <LinearLayout
+        android:id="@+id/header_layout"
         android:layout_height="@dimen/bubble_expanded_header_height"
-        android:gravity="start|center_vertical"
-        android:singleLine="true"
-        android:paddingLeft="@dimen/bubble_expanded_header_horizontal_padding"
-        android:paddingRight="@dimen/bubble_expanded_header_horizontal_padding"
-    />
+        android:layout_width="match_parent"
+        android:orientation="horizontal"
+        android:background="@drawable/bubble_expanded_header_bg">
+
+        <TextView
+            android:id="@+id/header_text"
+            android:textAppearance="@*android:style/TextAppearance.Material.Title"
+            android:textSize="18sp"
+            android:layout_weight="1"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:gravity="start|center_vertical"
+            android:singleLine="true"
+            android:paddingLeft="@dimen/bubble_expanded_header_horizontal_padding"
+            android:paddingRight="@dimen/bubble_expanded_header_horizontal_padding"
+        />
+
+        <ImageButton
+            android:id="@+id/deep_link_button"
+            android:layout_width="@dimen/bubble_header_icon_size"
+            android:layout_height="@dimen/bubble_header_icon_size"
+            android:gravity="end|center_vertical"
+            android:src="@drawable/ic_open_in_new"
+            android:scaleType="center"
+            android:tint="?android:attr/colorForeground"
+            android:background="?android:attr/selectableItemBackground"
+        />
+
+        <ImageButton
+            android:id="@id/settings_button"
+            android:layout_width="@dimen/bubble_header_icon_size"
+            android:layout_height="@dimen/bubble_header_icon_size"
+            android:src="@drawable/ic_settings"
+            android:gravity="end|center_vertical"
+            android:scaleType="center"
+            android:tint="?android:attr/colorForeground"
+            android:background="?android:attr/selectableItemBackground"
+        />
+
+    </LinearLayout>
 
 </com.android.systemui.bubbles.BubbleExpandedViewContainer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d435c67..8f5effb 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1030,4 +1030,6 @@
     <dimen name="bubble_stack_offscreen">5dp</dimen>
     <!-- How far down the screen the stack starts. -->
     <dimen name="bubble_stack_starting_offset_y">100dp</dimen>
+    <!-- Size of image buttons in the bubble header -->
+    <dimen name="bubble_header_icon_size">48dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ffa5de8..93a7acd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2346,4 +2346,12 @@
 
     <!-- What to show on the ambient display player when song doesn't have a title. [CHAR LIMIT=20] -->
     <string name="music_controls_no_title">No title</string>
+
+    <!-- Text used for content description of deep link button in the header of expanded bubble
+         view. [CHAR_LIMIT=NONE] -->
+    <string name="bubbles_deep_link_button_description">Open <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+    <!-- Text used for content description of settings button in the header of expanded bubble
+         view. [CHAR_LIMIT=NONE] -->
+    <string name="bubbles_settings_button_description">Open notification settings for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bcd41a0..0970c21 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -99,6 +99,7 @@
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.TimeZone;
+import java.util.function.Consumer;
 
 /**
  * Watches for updates that may be interesting to the keyguard, and provides
@@ -218,7 +219,8 @@
     // Battery status
     private BatteryStatus mBatteryStatus;
 
-    private final StrongAuthTracker mStrongAuthTracker;
+    @VisibleForTesting
+    protected StrongAuthTracker mStrongAuthTracker;
 
     private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
             mCallbacks = Lists.newArrayList();
@@ -344,8 +346,7 @@
                     handleUserUnlocked();
                     break;
                 case MSG_ASSISTANT_STACK_CHANGED:
-                    mAssistantVisible = (boolean)msg.obj;
-                    updateBiometricListeningState();
+                    setAssistantVisible((boolean) msg.obj);
                     break;
                 case MSG_BIOMETRIC_AUTHENTICATION_CONTINUE:
                     updateBiometricListeningState();
@@ -354,7 +355,7 @@
                     updateLogoutEnabled();
                     break;
                 case MSG_TELEPHONY_CAPABLE:
-                    updateTelephonyCapable((boolean)msg.obj);
+                    updateTelephonyCapable((boolean) msg.obj);
                     break;
                 default:
                     super.handleMessage(msg);
@@ -538,7 +539,8 @@
         }
     }
 
-    private void onFingerprintAuthenticated(int userId) {
+    @VisibleForTesting
+    protected void onFingerprintAuthenticated(int userId) {
         Trace.beginSection("KeyGuardUpdateMonitor#onFingerPrintAuthenticated");
         mUserFingerprintAuthenticated.put(userId, true);
         // Update/refresh trust state only if user can skip bouncer
@@ -693,7 +695,8 @@
         }
     }
 
-    private void onFaceAuthenticated(int userId) {
+    @VisibleForTesting
+    protected void onFaceAuthenticated(int userId) {
         Trace.beginSection("KeyGuardUpdateMonitor#onFaceAuthenticated");
         mUserFaceAuthenticated.put(userId, true);
         // Update/refresh trust state only if user can skip bouncer
@@ -898,8 +901,9 @@
 
 
     public boolean getUserCanSkipBouncer(int userId) {
-        return getUserHasTrust(userId) || (mUserFingerprintAuthenticated.get(userId)
-                && isUnlockingWithBiometricAllowed());
+        boolean fingerprintOrFace = mUserFingerprintAuthenticated.get(userId)
+                || mUserFaceAuthenticated.get(userId);
+        return getUserHasTrust(userId) || (fingerprintOrFace && isUnlockingWithBiometricAllowed());
     }
 
     public boolean getUserHasTrust(int userId) {
@@ -950,6 +954,12 @@
         }
     }
 
+    @VisibleForTesting
+    void setAssistantVisible(boolean assistantVisible) {
+        mAssistantVisible = assistantVisible;
+        updateBiometricListeningState();
+    }
+
     static class DisplayClientState {
         public int clientGeneration;
         public boolean clearing;
@@ -1047,7 +1057,8 @@
         }
     };
 
-    private final BroadcastReceiver mBroadcastAllReceiver = new BroadcastReceiver() {
+    @VisibleForTesting
+    protected final BroadcastReceiver mBroadcastAllReceiver = new BroadcastReceiver() {
 
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -1121,7 +1132,8 @@
         }
     };
 
-    private FaceManager.AuthenticationCallback mFaceAuthenticationCallback
+    @VisibleForTesting
+    FaceManager.AuthenticationCallback mFaceAuthenticationCallback
             = new FaceManager.AuthenticationCallback() {
 
         @Override
@@ -1298,9 +1310,13 @@
         }
     }
 
-    public class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
-        public StrongAuthTracker(Context context) {
+    public static class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+        private final Consumer<Integer> mStrongAuthRequiredChangedCallback;
+
+        public StrongAuthTracker(Context context,
+                Consumer<Integer> strongAuthRequiredChangedCallback) {
             super(context);
+            mStrongAuthRequiredChangedCallback = strongAuthRequiredChangedCallback;
         }
 
         public boolean isUnlockingWithBiometricAllowed() {
@@ -1316,7 +1332,7 @@
 
         @Override
         public void onStrongAuthRequiredChanged(int userId) {
-            notifyStrongAuthStateChanged(userId);
+            mStrongAuthRequiredChangedCallback.accept(userId);
         }
     }
 
@@ -1423,7 +1439,7 @@
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
-        mStrongAuthTracker = new StrongAuthTracker(context);
+        mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
 
         // Since device can't be un-provisioned, we only need to register a content observer
         // to update mDeviceProvisioned when we are...
@@ -1548,6 +1564,14 @@
         }
     }
 
+    /**
+     * Request passive authentication, when sensors detect that a user might be present.
+     */
+    public void onAuthInterruptDetected() {
+        if (DEBUG) Log.d(TAG, "onAuthInterruptDetected()");
+        updateFaceListeningState();
+    }
+
     private void updateFaceListeningState() {
         // If this message exists, we should not authenticate again until this message is
         // consumed by the handler
@@ -1585,11 +1609,13 @@
     }
 
     private boolean shouldListenForFace() {
-        return (mKeyguardIsVisible || !mDeviceInteractive ||
-                (mBouncer && !mKeyguardGoingAway) || mGoingToSleep ||
-                shouldListenForFaceAssistant() || (mKeyguardOccluded && mIsDreaming))
-                && !mSwitchingUser && !isFaceDisabled(getCurrentUser())
-                && !mKeyguardGoingAway && !mFaceLockedOut && mFaceSettingEnabledForUser;
+        final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep;
+        final int user = getCurrentUser();
+
+        return (mBouncer || awakeKeyguard || shouldListenForFaceAssistant())
+                && !mSwitchingUser && !getUserCanSkipBouncer(user) && !isFaceDisabled(user)
+                && !mKeyguardGoingAway && !mFaceLockedOut && mFaceSettingEnabledForUser
+                && mUserManager.isUserUnlocked(user);
     }
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
index 231e725..67b18fd 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
@@ -17,32 +17,53 @@
 package com.android.systemui.bubbles;
 
 import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.drawable.ShapeDrawable;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
+import android.widget.ImageButton;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.systemui.R;
 import com.android.systemui.recents.TriangleShape;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 /**
  * Container for the expanded bubble view, handles rendering the caret and header of the view.
  */
-public class BubbleExpandedViewContainer extends LinearLayout {
+public class BubbleExpandedViewContainer extends LinearLayout implements View.OnClickListener {
+    private static final String TAG = "BubbleExpandedView";
 
     // The triangle pointing to the expanded view
     private View mPointerView;
     // The view displayed between the pointer and the expanded view
     private TextView mHeaderView;
+    // Tappable header icon deeplinking into the app
+    private ImageButton mDeepLinkIcon;
+    // Tappable header icon deeplinking into notification settings
+    private ImageButton mSettingsIcon;
     // The view that is being displayed for the expanded state
     private View mExpandedView;
 
+    private NotificationEntry mEntry;
+    private PackageManager mPm;
+    private String mAppName;
+
+    // Need reference to let it know to collapse when new task is launched
+    private BubbleStackView mStackView;
+
     public BubbleExpandedViewContainer(Context context) {
         this(context, null);
     }
@@ -58,7 +79,7 @@
     public BubbleExpandedViewContainer(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        setOrientation(VERTICAL);
+        mPm = context.getPackageManager();
     }
 
     @Override
@@ -79,7 +100,75 @@
                 TriangleShape.create(width, height, true /* pointUp */));
         triangleDrawable.setTint(bgColor);
         mPointerView.setBackground(triangleDrawable);
-        mHeaderView = findViewById(R.id.bubble_content_header);
+
+        mHeaderView = findViewById(R.id.header_text);
+        mDeepLinkIcon = findViewById(R.id.deep_link_button);
+        mSettingsIcon = findViewById(R.id.settings_button);
+        mDeepLinkIcon.setOnClickListener(this);
+        mSettingsIcon.setOnClickListener(this);
+    }
+
+    /**
+     * Sets the notification entry used to populate this view.
+     */
+    public void setEntry(NotificationEntry entry, BubbleStackView stackView) {
+        mStackView = stackView;
+        mEntry = entry;
+
+        ApplicationInfo info;
+        try {
+            info = mPm.getApplicationInfo(
+                    entry.notification.getPackageName(),
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            | PackageManager.MATCH_DISABLED_COMPONENTS
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+            if (info != null) {
+                mAppName = String.valueOf(mPm.getApplicationLabel(info));
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            // Ahh... just use package name
+            mAppName = entry.notification.getPackageName();
+        }
+
+        updateHeaderView();
+    }
+
+    private void updateHeaderView() {
+        mSettingsIcon.setContentDescription(getResources().getString(
+                R.string.bubbles_settings_button_description, mAppName));
+        mDeepLinkIcon.setContentDescription(getResources().getString(
+                R.string.bubbles_deep_link_button_description, mAppName));
+        if (mEntry != null && mEntry.getBubbleMetadata() != null) {
+            setHeaderText(mEntry.getBubbleMetadata().getTitle());
+        } else {
+            // This should only happen if we're auto-bubbling notification content that isn't
+            // explicitly a bubble
+            setHeaderText(mAppName);
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (mEntry == null) {
+            return;
+        }
+        Notification n = mEntry.notification.getNotification();
+        int id = view.getId();
+        if (id == R.id.deep_link_button) {
+            mStackView.collapseStack(() -> {
+                try {
+                    n.contentIntent.send();
+                } catch (PendingIntent.CanceledException e) {
+                    Log.w(TAG, "Failed to send intent for bubble with key: "
+                            + (mEntry != null ? mEntry.key : " null entry"));
+                }
+            });
+        } else if (id == R.id.settings_button) {
+            Intent intent = getSettingsIntent(mEntry.notification.getPackageName(),
+                    mEntry.notification.getUid());
+            mStackView.collapseStack(() -> mContext.startActivity(intent));
+        }
     }
 
     /**
@@ -94,7 +183,7 @@
     /**
      * Set the text displayed within the header.
      */
-    public void setHeaderText(CharSequence text) {
+    private void setHeaderText(CharSequence text) {
         mHeaderView.setText(text);
         mHeaderView.setVisibility(TextUtils.isEmpty(text) ? GONE : VISIBLE);
     }
@@ -122,4 +211,13 @@
     public View getExpandedView() {
         return mExpandedView;
     }
+
+    private Intent getSettingsIntent(String packageName, final int appUid) {
+        final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+        intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
+        intent.putExtra(Settings.EXTRA_APP_UID, appUid);
+        intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        return intent;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index de322e6..c107058 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -20,7 +20,6 @@
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 import android.app.ActivityView;
-import android.app.PendingIntent;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Outline;
@@ -370,6 +369,12 @@
         }
     }
 
+    void collapseStack(Runnable endRunnable) {
+        collapseStack();
+        // TODO - use the runnable at end of animation
+        endRunnable.run();
+    }
+
     /**
      * Expands the stack fo bubbles.
      */
@@ -628,6 +633,7 @@
             return;
         }
 
+        mExpandedViewContainer.setEntry(mExpandedBubble.getEntry(), this);
         if (mExpandedBubble.hasAppOverlayIntent()) {
             // Bubble with activity view expanded state
             ActivityView expandedView = mExpandedBubble.getActivityView();
@@ -635,8 +641,6 @@
             expandedView.setLayoutParams(new LinearLayout.LayoutParams(
                     ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight));
 
-            final PendingIntent intent = mExpandedBubble.getAppOverlayIntent();
-            mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString());
             mExpandedViewContainer.setExpandedView(expandedView);
         } else {
             // Bubble with notification view expanded state
@@ -650,8 +654,6 @@
             } else {
                 mExpandedViewContainer.setExpandedView(null);
             }
-            // Bubble with notification as expanded state doesn't need a header / title
-            mExpandedViewContainer.setHeaderText(null);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index a784773..9bca3c7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -35,19 +35,19 @@
     private static final int SIZE = Build.IS_DEBUGGABLE ? 400 : 50;
     static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
 
-    private static final int REASONS = 10;
+    public static final int REASONS = 10;
 
     public static final int PULSE_REASON_NONE = -1;
     public static final int PULSE_REASON_INTENT = 0;
     public static final int PULSE_REASON_NOTIFICATION = 1;
     public static final int PULSE_REASON_SENSOR_SIGMOTION = 2;
-    public static final int PULSE_REASON_SENSOR_PICKUP = 3;
-    public static final int PULSE_REASON_SENSOR_DOUBLE_TAP = 4;
+    public static final int REASON_SENSOR_PICKUP = 3;
+    public static final int REASON_SENSOR_DOUBLE_TAP = 4;
     public static final int PULSE_REASON_SENSOR_LONG_PRESS = 5;
     public static final int PULSE_REASON_DOCKING = 6;
     public static final int REASON_SENSOR_WAKE_UP = 7;
     public static final int PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN = 8;
-    public static final int PULSE_REASON_SENSOR_TAP = 9;
+    public static final int REASON_SENSOR_TAP = 9;
 
     private static boolean sRegisterKeyguardCallback = true;
 
@@ -202,13 +202,13 @@
             case PULSE_REASON_INTENT: return "intent";
             case PULSE_REASON_NOTIFICATION: return "notification";
             case PULSE_REASON_SENSOR_SIGMOTION: return "sigmotion";
-            case PULSE_REASON_SENSOR_PICKUP: return "pickup";
-            case PULSE_REASON_SENSOR_DOUBLE_TAP: return "doubletap";
+            case REASON_SENSOR_PICKUP: return "pickup";
+            case REASON_SENSOR_DOUBLE_TAP: return "doubletap";
             case PULSE_REASON_SENSOR_LONG_PRESS: return "longpress";
             case PULSE_REASON_DOCKING: return "docking";
             case PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN: return "wakelockscreen";
             case REASON_SENSOR_WAKE_UP: return "wakeup";
-            case PULSE_REASON_SENSOR_TAP: return "tap";
+            case REASON_SENSOR_TAP: return "tap";
             default: throw new IllegalArgumentException("bad reason: " + pulseReason);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 675948e..5efdc2f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -97,20 +97,20 @@
                         mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
                         Settings.Secure.DOZE_PICK_UP_GESTURE,
                         config.dozePickupSensorAvailable(),
-                        DozeLog.PULSE_REASON_SENSOR_PICKUP, false /* touchCoords */,
+                        DozeLog.REASON_SENSOR_PICKUP, false /* touchCoords */,
                         false /* touchscreen */),
                 new TriggerSensor(
                         findSensorWithType(config.doubleTapSensorType()),
                         Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
                         true /* configured */,
-                        DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
+                        DozeLog.REASON_SENSOR_DOUBLE_TAP,
                         dozeParameters.doubleTapReportsTouchCoordinates(),
                         true /* touchscreen */),
                 new TriggerSensor(
                         findSensorWithType(config.tapSensorType()),
                         Settings.Secure.DOZE_TAP_SCREEN_GESTURE,
                         true /* configured */,
-                        DozeLog.PULSE_REASON_SENSOR_TAP,
+                        DozeLog.REASON_SENSOR_TAP,
                         false /* reports touch coordinates */,
                         true /* touchscreen */),
                 new TriggerSensor(
@@ -530,7 +530,7 @@
 
         /**
          * Called when a sensor requests a pulse
-         * @param pulseReason Requesting sensor, e.g. {@link DozeLog#PULSE_REASON_SENSOR_PICKUP}
+         * @param pulseReason Requesting sensor, e.g. {@link DozeLog#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.
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index dc505b5..8a35e70 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -108,10 +108,6 @@
         DozeLog.traceNotificationPulse(mContext);
     }
 
-    private void onWhisper() {
-        requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */);
-    }
-
     private void proximityCheckThenCall(IntConsumer callback,
             boolean alreadyPerformedProxCheck,
             int reason) {
@@ -137,9 +133,9 @@
     @VisibleForTesting
     void onSensor(int pulseReason, boolean sensorPerformedProxCheck,
             float screenX, float screenY, float[] rawValues) {
-        boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
-        boolean isTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_TAP;
-        boolean isPickup = pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP;
+        boolean isDoubleTap = pulseReason == DozeLog.REASON_SENSOR_DOUBLE_TAP;
+        boolean isTap = pulseReason == DozeLog.REASON_SENSOR_TAP;
+        boolean isPickup = pulseReason == DozeLog.REASON_SENSOR_PICKUP;
         boolean isLongPress = pulseReason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS;
         boolean isWakeDisplay = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP;
         boolean isWakeLockScreen = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
index f0d804d..3db02b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
@@ -54,7 +54,7 @@
     public static NotificationUiAdjustment extractFromNotificationEntry(
             NotificationEntry entry) {
         return new NotificationUiAdjustment(
-                entry.key, entry.systemGeneratedSmartActions, entry.smartReplies);
+                entry.key, entry.systemGeneratedSmartActions, entry.systemGeneratedSmartReplies);
     }
 
     public static boolean needReinflate(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 2e93c382..db9fcc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -102,7 +102,9 @@
     /** Smart Actions provided by the NotificationAssistantService. */
     @NonNull
     public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
-    public CharSequence[] smartReplies = new CharSequence[0];
+    /** Smart replies provided by the NotificationAssistantService. */
+    @NonNull
+    public CharSequence[] systemGeneratedSmartReplies = new CharSequence[0];
     @VisibleForTesting
     public int suppressedVisualEffects;
     public boolean suspended;
@@ -182,7 +184,7 @@
         userSentiment = ranking.getUserSentiment();
         systemGeneratedSmartActions = ranking.getSmartActions() == null
                 ? Collections.emptyList() : ranking.getSmartActions();
-        smartReplies = ranking.getSmartReplies() == null
+        systemGeneratedSmartReplies = ranking.getSmartReplies() == null
                 ? new CharSequence[0]
                 : ranking.getSmartReplies().toArray(new CharSequence[0]);
         suppressedVisualEffects = ranking.getSuppressedVisualEffects();
@@ -239,6 +241,13 @@
     }
 
     /**
+     * Returns the data needed for a bubble for this notification, if it exists.
+     */
+    public Notification.BubbleMetadata getBubbleMetadata() {
+        return notification.getNotification().getBubbleMetadata();
+    }
+
+    /**
      * Resets the notification entry to be re-used.
      */
     public void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 0a04f4d..878d533 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1373,13 +1373,13 @@
         }
         // Apps didn't provide any smart replies / actions, use those from NAS (if any).
         if (!appGeneratedSmartRepliesExist && !appGeneratedSmartActionsExist) {
-            boolean useGeneratedReplies = !ArrayUtils.isEmpty(entry.smartReplies)
+            boolean useGeneratedReplies = !ArrayUtils.isEmpty(entry.systemGeneratedSmartReplies)
                     && freeformRemoteInputActionPair != null
                     && freeformRemoteInputActionPair.second.getAllowGeneratedReplies()
                     && freeformRemoteInputActionPair.second.actionIntent != null;
             if (useGeneratedReplies) {
                 smartReplies = new SmartReplyView.SmartReplies(
-                        entry.smartReplies,
+                        entry.systemGeneratedSmartReplies,
                         freeformRemoteInputActionPair.first,
                         freeformRemoteInputActionPair.second.actionIntent,
                         true /* fromAssistant */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 302d630..304d2ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -47,7 +47,7 @@
     private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock wakelock";
 
     /**
-     * Mode in which we don't need to wake up the device when we get a fingerprint.
+     * Mode in which we don't need to wake up the device when we authenticate.
      */
     public static final int MODE_NONE = 0;
 
@@ -70,8 +70,7 @@
     public static final int MODE_SHOW_BOUNCER = 3;
 
     /**
-     * Mode in which we only wake up the device, and keyguard was not showing when we acquired a
-     * fingerprint.
+     * Mode in which we only wake up the device, and keyguard was not showing when we authenticated.
      * */
     public static final int MODE_ONLY_WAKE = 4;
 
@@ -96,22 +95,21 @@
      */
     private static final float BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR = 1.1f;
 
-    private final NotificationMediaManager mMediaManager =
-            Dependency.get(NotificationMediaManager.class);
-    private PowerManager mPowerManager;
-    private Handler mHandler = new Handler();
+    private final NotificationMediaManager mMediaManager;
+    private final PowerManager mPowerManager;
+    private final Handler mHandler;
     private PowerManager.WakeLock mWakeLock;
-    private KeyguardUpdateMonitor mUpdateMonitor;
+    private final KeyguardUpdateMonitor mUpdateMonitor;
+    private final UnlockMethodCache mUnlockMethodCache;
+    private final StatusBarWindowController mStatusBarWindowController;
+    private final Context mContext;
+    private final int mWakeUpDelay;
     private int mMode;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private StatusBarWindowController mStatusBarWindowController;
     private DozeScrimController mDozeScrimController;
     private KeyguardViewMediator mKeyguardViewMediator;
     private ScrimController mScrimController;
     private StatusBar mStatusBar;
-    private final UnlockMethodCache mUnlockMethodCache;
-    private final Context mContext;
-    private final int mWakeUpDelay;
     private int mPendingAuthenticatedUserId = -1;
     private BiometricSourceType mPendingAuthenticatedBioSourceType = null;
     private boolean mPendingShowBouncer;
@@ -122,11 +120,14 @@
                                      KeyguardViewMediator keyguardViewMediator,
                                      ScrimController scrimController,
                                      StatusBar statusBar,
-                                     UnlockMethodCache unlockMethodCache) {
+                                     UnlockMethodCache unlockMethodCache, Handler handler,
+                                     KeyguardUpdateMonitor keyguardUpdateMonitor,
+                                     int wakeUpDelay) {
         mContext = context;
         mPowerManager = context.getSystemService(PowerManager.class);
-        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+        mUpdateMonitor = keyguardUpdateMonitor;
         mUpdateMonitor.registerCallback(this);
+        mMediaManager = Dependency.get(NotificationMediaManager.class);
         Dependency.get(WakefulnessLifecycle.class).addObserver(mWakefulnessObserver);
         Dependency.get(ScreenLifecycle.class).addObserver(mScreenObserver);
         mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
@@ -135,8 +136,8 @@
         mScrimController = scrimController;
         mStatusBar = statusBar;
         mUnlockMethodCache = unlockMethodCache;
-        mWakeUpDelay = context.getResources().getInteger(
-                com.android.internal.R.integer.config_wakeUpDelayDoze);
+        mHandler = handler;
+        mWakeUpDelay = wakeUpDelay;
     }
 
     public void setStatusBarKeyguardViewManager(
@@ -206,7 +207,7 @@
             Trace.endSection();
             return;
         }
-        startWakeAndUnlock(calculateMode());
+        startWakeAndUnlock(calculateMode(biometricSourceType));
     }
 
     public void startWakeAndUnlock(int mode) {
@@ -295,7 +296,7 @@
     }
 
     private void showBouncer() {
-        if (calculateMode() == MODE_SHOW_BOUNCER) {
+        if (mMode == MODE_SHOW_BOUNCER) {
             mStatusBarKeyguardViewManager.showBouncer(false);
         }
         mStatusBarKeyguardViewManager.animateCollapsePanels(
@@ -339,29 +340,30 @@
         return mMode;
     }
 
-    private int calculateMode() {
+    private int calculateMode(BiometricSourceType biometricSourceType) {
         boolean unlockingAllowed = mUpdateMonitor.isUnlockingWithBiometricAllowed();
         boolean deviceDreaming = mUpdateMonitor.isDreaming();
+        boolean isFace = biometricSourceType == BiometricSourceType.FACE;
 
         if (!mUpdateMonitor.isDeviceInteractive()) {
             if (!mStatusBarKeyguardViewManager.isShowing()) {
                 return MODE_ONLY_WAKE;
             } else if (mDozeScrimController.isPulsing() && unlockingAllowed) {
-                return MODE_WAKE_AND_UNLOCK_PULSING;
+                return isFace ? MODE_NONE : MODE_WAKE_AND_UNLOCK_PULSING;
             } else if (unlockingAllowed || !mUnlockMethodCache.isMethodSecure()) {
                 return MODE_WAKE_AND_UNLOCK;
             } else {
                 return MODE_SHOW_BOUNCER;
             }
         }
-        if (unlockingAllowed && deviceDreaming) {
+        if (unlockingAllowed && deviceDreaming && !isFace) {
             return MODE_WAKE_AND_UNLOCK_FROM_DREAM;
         }
         if (mStatusBarKeyguardViewManager.isShowing()) {
             if (mStatusBarKeyguardViewManager.isBouncerShowing() && unlockingAllowed) {
                 return MODE_DISMISS_BOUNCER;
             } else if (unlockingAllowed) {
-                return MODE_UNLOCK;
+                return isFace ? MODE_ONLY_WAKE : MODE_UNLOCK;
             } else if (!mStatusBarKeyguardViewManager.isBouncerShowing()) {
                 return MODE_SHOW_BOUNCER;
             }
@@ -437,7 +439,7 @@
     }
 
     /**
-     * Successful authentication with fingerprint that wakes up the device.
+     * Successful authentication with fingerprint, face, or iris that wakes up the device.
      */
     public boolean isWakeAndUnlock() {
         return mMode == MODE_WAKE_AND_UNLOCK
@@ -446,7 +448,8 @@
     }
 
     /**
-     * Successful authentication with fingerprint when the screen was either on or off.
+     * Successful authentication with fingerprint, face, or iris when the screen was either
+     * on or off.
      */
     public boolean isBiometricUnlock() {
         return isWakeAndUnlock() || mMode == MODE_UNLOCK;
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 8796e0a..c129143 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -354,6 +354,8 @@
     protected StatusBarWindowController mStatusBarWindowController;
     protected UnlockMethodCache mUnlockMethodCache;
     @VisibleForTesting
+    KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @VisibleForTesting
     DozeServiceHost mDozeServiceHost = new DozeServiceHost();
     private boolean mWakeUpComingFromTouch;
     private PointF mWakeUpTouchLocation;
@@ -674,7 +676,7 @@
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
 
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-
+        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
@@ -761,7 +763,7 @@
         mUnlockMethodCache.addListener(this);
         startKeyguard();
 
-        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateCallback);
+        mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
         putComponent(DozeHost.class, mDozeServiceHost);
 
         mScreenPinningRequest = new ScreenPinningRequest(mContext);
@@ -1206,7 +1208,9 @@
         KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
         mBiometricUnlockController = new BiometricUnlockController(mContext,
                 mDozeScrimController, keyguardViewMediator,
-                mScrimController, this, UnlockMethodCache.getInstance(mContext));
+                mScrimController, this, UnlockMethodCache.getInstance(mContext),
+                new Handler(), mKeyguardUpdateMonitor, mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_wakeUpDelayDoze));
         mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
                 getBouncerContainer(), mNotificationPanel, mBiometricUnlockController);
         mKeyguardIndicationController
@@ -2357,8 +2361,8 @@
             mLightBarController.dump(fd, pw, args);
         }
 
-        if (KeyguardUpdateMonitor.getInstance(mContext) != null) {
-            KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args);
+        if (mKeyguardUpdateMonitor != null) {
+            mKeyguardUpdateMonitor.dump(fd, pw, args);
         }
 
         FalsingManager.getInstance(mContext).dump(pw);
@@ -3891,6 +3895,11 @@
                 return;
             }
 
+            if (mKeyguardUpdateMonitor != null
+                    && reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+                mKeyguardUpdateMonitor.onAuthInterruptDetected();
+            }
+
             // Set the state to pulsing, so ScrimController will know what to do once we ask it to
             // execute the transition. The pulse callback will then be invoked when the scrims
             // are black, indicating that StatusBar is ready to present the rest of the UI.
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 2055519..d5a275e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -18,15 +18,33 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.google.common.truth.Truth.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.app.admin.DevicePolicyManager;
+import android.app.trust.TrustManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
 import android.os.Bundle;
+import android.os.UserManager;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
@@ -39,6 +57,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -52,37 +72,71 @@
 @RunWithLooper(setAsMainLooper = true)
 public class KeyguardUpdateMonitorTest extends SysuiTestCase {
 
+    @Mock
+    private KeyguardUpdateMonitor.StrongAuthTracker mStrongAuthTracker;
+    @Mock
+    private TrustManager mTrustManager;
+    @Mock
+    private FingerprintManager mFingerprintManager;
+    @Mock
+    private FaceManager mFaceManager;
+    @Mock
+    private BiometricManager mBiometricManager;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private DevicePolicyManager mDevicePolicyManager;
     private TestableLooper mTestableLooper;
+    private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     @Before
     public void setup() {
+        MockitoAnnotations.initMocks(this);
+        TestableContext context = spy(mContext);
+        when(mPackageManager.hasSystemFeature(anyString())).thenReturn(true);
+        when(context.getPackageManager()).thenReturn(mPackageManager);
+        doAnswer(invocation -> {
+            IBiometricEnabledOnKeyguardCallback callback = invocation.getArgument(0);
+            callback.onChanged(BiometricSourceType.FACE, true /* enabled */);
+            return null;
+        }).when(mBiometricManager).registerEnabledOnKeyguardCallback(any());
+        when(mFaceManager.isHardwareDetected()).thenReturn(true);
+        when(mFaceManager.hasEnrolledTemplates()).thenReturn(true);
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+        when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        context.addMockSystemService(TrustManager.class, mTrustManager);
+        context.addMockSystemService(FingerprintManager.class, mFingerprintManager);
+        context.addMockSystemService(BiometricManager.class, mBiometricManager);
+        context.addMockSystemService(FaceManager.class, mFaceManager);
+        context.addMockSystemService(UserManager.class, mUserManager);
+        context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
+
         mTestableLooper = TestableLooper.get(this);
+        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(context);
     }
 
     @Test
     public void testIgnoresSimStateCallback_rebroadcast() {
         Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
 
-        TestableKeyguardUpdateMonitor keyguardUpdateMonitor =
-                new TestableKeyguardUpdateMonitor(getContext());
-
-        keyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), intent);
+        mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), intent);
         mTestableLooper.processAllMessages();
         Assert.assertTrue("onSimStateChanged not called",
-                keyguardUpdateMonitor.hasSimStateJustChanged());
+                mKeyguardUpdateMonitor.hasSimStateJustChanged());
 
         intent.putExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, true);
-        keyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), intent);
+        mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(), intent);
         mTestableLooper.processAllMessages();
         Assert.assertFalse("onSimStateChanged should have been skipped",
-                keyguardUpdateMonitor.hasSimStateJustChanged());
+                mKeyguardUpdateMonitor.hasSimStateJustChanged());
     }
 
     @Test
     public void testTelephonyCapable_BootInitState() {
-        TestableKeyguardUpdateMonitor keyguardUpdateMonitor =
-                new TestableKeyguardUpdateMonitor(getContext());
-        assertThat(keyguardUpdateMonitor.mTelephonyCapable).isFalse();
+        assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isFalse();
     }
 
     @Test
@@ -90,36 +144,30 @@
         Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
         intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE
                 , IccCardConstants.INTENT_VALUE_ICC_ABSENT);
-        TestableKeyguardUpdateMonitor keyguardUpdateMonitor =
-                new TestableKeyguardUpdateMonitor(getContext());
-        keyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
+        mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
                 , putPhoneInfo(intent,null, false));
         mTestableLooper.processAllMessages();
-        assertThat(keyguardUpdateMonitor.mTelephonyCapable).isTrue();
+        assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isTrue();
     }
 
     @Test
     public void testTelephonyCapable_SimInvalid_ServiceState_InService() {
         // SERVICE_STATE - IN_SERVICE, but SIM_STATE is invalid TelephonyCapable should be False
-        TestableKeyguardUpdateMonitor keyguardUpdateMonitor =
-                new TestableKeyguardUpdateMonitor(getContext());
         Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
         Bundle data = new Bundle();
         ServiceState state = new ServiceState();
         state.setState(ServiceState.STATE_IN_SERVICE);
         state.fillInNotifierBundle(data);
-        keyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
+        mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
                 , putPhoneInfo(intent, data, false));
         mTestableLooper.processAllMessages();
-        assertThat(keyguardUpdateMonitor.mTelephonyCapable).isFalse();
+        assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isFalse();
     }
 
     @Test
     public void testTelephonyCapable_SimValid_ServiceState_PowerOff() {
         // Simulate AirplaneMode case, SERVICE_STATE - POWER_OFF, check TelephonyCapable False
         // Only receive ServiceState callback IN_SERVICE -> OUT_OF_SERVICE -> POWER_OFF
-        TestableKeyguardUpdateMonitor keyguardUpdateMonitor =
-                new TestableKeyguardUpdateMonitor(getContext());
         Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
         intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE
                 , IccCardConstants.INTENT_VALUE_ICC_LOADED);
@@ -127,10 +175,10 @@
         ServiceState state = new ServiceState();
         state.setState(ServiceState.STATE_POWER_OFF);
         state.fillInNotifierBundle(data);
-        keyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
+        mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
                 , putPhoneInfo(intent, data, true));
         mTestableLooper.processAllMessages();
-        assertThat(keyguardUpdateMonitor.mTelephonyCapable).isTrue();
+        assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isTrue();
     }
 
     /* Normal SIM inserted flow
@@ -142,24 +190,20 @@
      */
     @Test
     public void testTelephonyCapable_BootInitState_ServiceState_OutOfService() {
-        TestableKeyguardUpdateMonitor keyguardUpdateMonitor =
-                new TestableKeyguardUpdateMonitor(getContext());
         Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
         Bundle data = new Bundle();
         ServiceState state = new ServiceState();
         state.setState(ServiceState.STATE_OUT_OF_SERVICE);
         state.fillInNotifierBundle(data);
         intent.putExtras(data);
-        keyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
+        mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
                 , putPhoneInfo(intent, data, false));
         mTestableLooper.processAllMessages();
-        assertThat(keyguardUpdateMonitor.mTelephonyCapable).isFalse();
+        assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isFalse();
     }
 
     @Test
     public void testTelephonyCapable_BootInitState_SimState_NotReady() {
-        TestableKeyguardUpdateMonitor keyguardUpdateMonitor =
-                new TestableKeyguardUpdateMonitor(getContext());
         Bundle data = new Bundle();
         ServiceState state = new ServiceState();
         state.setState(ServiceState.STATE_OUT_OF_SERVICE);
@@ -167,16 +211,14 @@
         Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
         intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE
                 , IccCardConstants.INTENT_VALUE_ICC_NOT_READY);
-        keyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
+        mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
                 , putPhoneInfo(intent, data, false));
         mTestableLooper.processAllMessages();
-        assertThat(keyguardUpdateMonitor.mTelephonyCapable).isFalse();
+        assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isFalse();
     }
 
     @Test
     public void testTelephonyCapable_BootInitState_SimState_Ready() {
-        TestableKeyguardUpdateMonitor keyguardUpdateMonitor =
-                new TestableKeyguardUpdateMonitor(getContext());
         Bundle data = new Bundle();
         ServiceState state = new ServiceState();
         state.setState(ServiceState.STATE_OUT_OF_SERVICE);
@@ -184,46 +226,40 @@
         Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
         intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE
                 , IccCardConstants.INTENT_VALUE_ICC_READY);
-        keyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
+        mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
                 , putPhoneInfo(intent, data, false));
         mTestableLooper.processAllMessages();
-        assertThat(keyguardUpdateMonitor.mTelephonyCapable).isFalse();
+        assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isFalse();
     }
 
     @Test
     public void testTelephonyCapable_BootInitState_ServiceState_PowerOff() {
-        TestableKeyguardUpdateMonitor keyguardUpdateMonitor =
-                new TestableKeyguardUpdateMonitor(getContext());
         Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
         Bundle data = new Bundle();
         ServiceState state = new ServiceState();
         state.setState(ServiceState.STATE_POWER_OFF);
         state.fillInNotifierBundle(data);
-        keyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
+        mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
                 , putPhoneInfo(intent, data, false));
         mTestableLooper.processAllMessages();
-        assertThat(keyguardUpdateMonitor.mTelephonyCapable).isFalse();
+        assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isFalse();
     }
 
     @Test
     public void testTelephonyCapable_SimValid_ServiceState_InService() {
-        TestableKeyguardUpdateMonitor keyguardUpdateMonitor =
-                new TestableKeyguardUpdateMonitor(getContext());
         Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
         Bundle data = new Bundle();
         ServiceState state = new ServiceState();
         state.setState(ServiceState.STATE_IN_SERVICE);
         state.fillInNotifierBundle(data);
-        keyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
+        mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
                 , putPhoneInfo(intent, data, true));
         mTestableLooper.processAllMessages();
-        assertThat(keyguardUpdateMonitor.mTelephonyCapable).isTrue();
+        assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isTrue();
     }
 
     @Test
     public void testTelephonyCapable_SimValid_SimState_Loaded() {
-        TestableKeyguardUpdateMonitor keyguardUpdateMonitor =
-                new TestableKeyguardUpdateMonitor(getContext());
         Bundle data = new Bundle();
         ServiceState state = new ServiceState();
         state.setState(ServiceState.STATE_IN_SERVICE);
@@ -231,19 +267,96 @@
         Intent intentSimState = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
         intentSimState.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE
                 , IccCardConstants.INTENT_VALUE_ICC_LOADED);
-        keyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
+        mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
                 , putPhoneInfo(intentSimState, data, true));
         mTestableLooper.processAllMessages();
         // Even SimState Loaded, still need ACTION_SERVICE_STATE_CHANGED turn on mTelephonyCapable
-        assertThat(keyguardUpdateMonitor.mTelephonyCapable).isFalse();
+        assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isFalse();
 
         Intent intentServiceState =  new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
         intentSimState.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE
                 , IccCardConstants.INTENT_VALUE_ICC_LOADED);
-        keyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
+        mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
                 , putPhoneInfo(intentServiceState, data, true));
         mTestableLooper.processAllMessages();
-        assertThat(keyguardUpdateMonitor.mTelephonyCapable).isTrue();
+        assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isTrue();
+    }
+
+    @Test
+    public void testTriesToAuthenticate_whenBouncer() {
+        mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true);
+        mTestableLooper.processAllMessages();
+
+        verify(mFaceManager).authenticate(any(), any(), anyInt(), any(), any());
+        verify(mFaceManager).isHardwareDetected();
+        verify(mFaceManager).hasEnrolledTemplates(anyInt());
+    }
+
+    @Test
+    public void testTriesToAuthenticate_whenKeyguard() {
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+        mTestableLooper.processAllMessages();
+        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        verify(mFaceManager).authenticate(any(), any(), anyInt(), any(), any());
+    }
+
+    @Test
+    public void skipsAuthentication_whenEncryptedKeyguard() {
+        reset(mUserManager);
+        when(mUserManager.isUserUnlocked(anyInt())).thenReturn(false);
+
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+        mTestableLooper.processAllMessages();
+        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+        verify(mFaceManager, never()).authenticate(any(), any(), anyInt(), any(), any());
+    }
+
+    @Test
+    public void testTriesToAuthenticate_whenAssistant() {
+        mKeyguardUpdateMonitor.setKeyguardOccluded(true);
+        mKeyguardUpdateMonitor.setAssistantVisible(true);
+
+        verify(mFaceManager).authenticate(any(), any(), anyInt(), any(), any());
+    }
+
+    @Test
+    public void testNeverAuthenticates_whenFaceLockout() {
+        mKeyguardUpdateMonitor.mFaceAuthenticationCallback
+                .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT, "lockout");
+        mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true);
+        mTestableLooper.processAllMessages();
+
+        verify(mFaceManager, never()).authenticate(any(), any(), anyInt(), any(), any());
+    }
+
+    @Test
+    public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() {
+        mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser());
+        mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true);
+        mTestableLooper.processAllMessages();
+
+        verify(mFaceManager, never()).authenticate(any(), any(), anyInt(), any(), any());
+    }
+
+    @Test
+    public void testGetUserCanSkipBouncer_whenFace() {
+        int user = KeyguardUpdateMonitor.getCurrentUser();
+        mKeyguardUpdateMonitor.onFaceAuthenticated(user);
+        assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
+    }
+
+    @Test
+    public void testGetUserCanSkipBouncer_whenFingerprint() {
+        int user = KeyguardUpdateMonitor.getCurrentUser();
+        mKeyguardUpdateMonitor.onFingerprintAuthenticated(user);
+        assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
+    }
+
+    @Test
+    public void testGetUserCanSkipBouncer_whenTrust() {
+        int user = KeyguardUpdateMonitor.getCurrentUser();
+        mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, user, 0 /* flags */);
+        assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
 
     private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) {
@@ -261,8 +374,9 @@
 
         protected TestableKeyguardUpdateMonitor(Context context) {
             super(context);
-            // Avoid race condition when unexpected broadcast could be received.
             context.unregisterReceiver(mBroadcastReceiver);
+            context.unregisterReceiver(mBroadcastAllReceiver);
+            mStrongAuthTracker = KeyguardUpdateMonitorTest.this.mStrongAuthTracker;
         }
 
         public boolean hasSimStateJustChanged() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index e1c481e..5a6200f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -230,9 +230,9 @@
     public void testPulseReason_getMatchesRequest() {
         mMachine.requestState(INITIALIZED);
         mMachine.requestState(DOZE);
-        mMachine.requestPulse(DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP);
+        mMachine.requestPulse(DozeLog.REASON_SENSOR_DOUBLE_TAP);
 
-        assertEquals(DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP, mMachine.getPulseReason());
+        assertEquals(DozeLog.REASON_SENSOR_DOUBLE_TAP, mMachine.getPulseReason());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 7b358b9..cdac7c97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -125,7 +125,7 @@
     public void testOnSensor_whenUndockedWithNearAndDoubleTapScreen_shouldNotWakeUp() {
         mSensors.getMockProximitySensor().sendProximityResult(false /* far */);
 
-        mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
+        mTriggers.onSensor(DozeLog.REASON_SENSOR_DOUBLE_TAP,
                 false /* sensorPerformedProxCheck */, 50 /* screenX */, 50 /* screenY */,
                 null /* rawValues */);
 
@@ -137,7 +137,7 @@
         doReturn(true).when(mDockManagerFake).isDocked();
         mSensors.getMockProximitySensor().sendProximityResult(false /* far */);
 
-        mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
+        mTriggers.onSensor(DozeLog.REASON_SENSOR_DOUBLE_TAP,
                 false /* sensorPerformedProxCheck */, 50 /* screenX */, 50 /* screenY */,
                 null /* rawValues */);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index c36fec2..c80396d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -209,7 +209,8 @@
 
     @Test
     public void chooseSmartRepliesAndActions_smartRepliesOff_noSystemGeneratedSmartSuggestions() {
-        mEntry.smartReplies = new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
+        mEntry.systemGeneratedSmartReplies =
+                new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
         mEntry.systemGeneratedSmartActions =
                 createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
         when(mSmartReplyConstants.isEnabled()).thenReturn(false);
@@ -256,11 +257,13 @@
         // replies.
         setupAppGeneratedReplies(null /* smartReplies */);
 
-        mEntry.smartReplies = new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
+        mEntry.systemGeneratedSmartReplies =
+                new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
         NotificationContentView.SmartRepliesAndActions repliesAndActions =
                 NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
 
-        assertThat(repliesAndActions.smartReplies.choices).isEqualTo(mEntry.smartReplies);
+        assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
+                mEntry.systemGeneratedSmartReplies);
         assertThat(repliesAndActions.smartReplies.fromAssistant).isTrue();
         assertThat(repliesAndActions.smartActions).isNull();
     }
@@ -271,7 +274,7 @@
         // replies.
         setupAppGeneratedReplies(null /* smartReplies */, false /* allowSystemGeneratedReplies */);
 
-        mEntry.smartReplies =
+        mEntry.systemGeneratedSmartReplies =
                 new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
         NotificationContentView.SmartRepliesAndActions repliesAndActions =
                 NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
@@ -306,7 +309,8 @@
                 createActions(new String[] {"Test Action 1", "Test Action 2"});
         setupAppGeneratedSuggestions(appGenSmartReplies, appGenSmartActions);
 
-        mEntry.smartReplies = new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
+        mEntry.systemGeneratedSmartReplies =
+                new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
         mEntry.systemGeneratedSmartActions =
                 createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
 
@@ -325,7 +329,8 @@
         // actions.
         setupAppGeneratedReplies(null /* smartReplies */, false /* allowSystemGeneratedReplies */);
         when(mNotification.getAllowSystemGeneratedContextualActions()).thenReturn(false);
-        mEntry.smartReplies = new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
+        mEntry.systemGeneratedSmartReplies =
+                new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
         mEntry.systemGeneratedSmartActions =
                 createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
new file mode 100644
index 0000000..7be4756
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2019 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.statusbar.phone;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricSourceType;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.statusbar.NotificationMediaManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class BiometricsUnlockControllerTest extends SysuiTestCase {
+
+    @Mock
+    private NotificationMediaManager mMediaManager;
+    @Mock
+    private PowerManager mPowerManager;
+    @Mock
+    private KeyguardUpdateMonitor mUpdateMonitor;
+    @Mock
+    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock
+    private StatusBarWindowController mStatusBarWindowController;
+    @Mock
+    private DozeScrimController mDozeScrimController;
+    @Mock
+    private KeyguardViewMediator mKeyguardViewMediator;
+    @Mock
+    private ScrimController mScrimController;
+    @Mock
+    private StatusBar mStatusBar;
+    @Mock
+    private UnlockMethodCache mUnlockMethodCache;
+    private BiometricUnlockController mBiometricUnlockController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+        when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
+        mContext.addMockSystemService(PowerManager.class, mPowerManager);
+        mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
+        mDependency.injectTestDependency(StatusBarWindowController.class,
+                mStatusBarWindowController);
+        mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController,
+                mKeyguardViewMediator, mScrimController, mStatusBar, mUnlockMethodCache,
+                new Handler(), mUpdateMonitor, 0 /* wakeUpDelay */);
+        mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
+    }
+
+    @Test
+    public void onBiometricAuthenticated_whenFingerprintAndBiometricsDisallowed_showBouncer() {
+        mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
+                BiometricSourceType.FINGERPRINT);
+        verify(mStatusBarKeyguardViewManager).showBouncer(eq(false));
+        verify(mStatusBarKeyguardViewManager).animateCollapsePanels(anyFloat());
+    }
+
+    @Test
+    public void onBiometricAuthenticated_whenFingerprintAndNotInteractive_wakeAndUnlock() {
+        reset(mUpdateMonitor);
+        reset(mStatusBarKeyguardViewManager);
+        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mDozeScrimController.isPulsing()).thenReturn(true);
+        mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
+                BiometricSourceType.FINGERPRINT);
+
+        verify(mKeyguardViewMediator).onWakeAndUnlocking();
+    }
+
+    @Test
+    public void onBiometricAuthenticated_whenFingerprint_dismissKeyguard() {
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
+                BiometricSourceType.FINGERPRINT);
+
+        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager).animateCollapsePanels(anyFloat());
+    }
+
+    @Test
+    public void onBiometricAuthenticated_whenFingerprintOnBouncer_dismissBouncer() {
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+        mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
+                BiometricSourceType.FINGERPRINT);
+
+        verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+    }
+
+    @Test
+    public void onBiometricAuthenticated_whenFace_dontDismissKeyguard() {
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
+                BiometricSourceType.FACE);
+
+        verify(mStatusBarKeyguardViewManager, never()).animateCollapsePanels(anyFloat());
+    }
+
+    @Test
+    public void onBiometricAuthenticated_whenFaceOnBouncer_dismissBouncer() {
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+        mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
+                BiometricSourceType.FACE);
+
+        verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+    }
+
+    @Test
+    public void onBiometricAuthenticated_whenFaceAndPulsing_dontDismissKeyguard() {
+        reset(mUpdateMonitor);
+        reset(mStatusBarKeyguardViewManager);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mDozeScrimController.isPulsing()).thenReturn(true);
+        mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
+                BiometricSourceType.FACE);
+
+        verify(mStatusBarKeyguardViewManager, never()).animateCollapsePanels(anyFloat());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 17611ff..49fcafd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -64,6 +64,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.logging.testing.FakeMetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.InitController;
@@ -120,6 +121,9 @@
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -161,6 +165,8 @@
     private NotificationAlertingManager mNotificationAlertingManager;
     @Mock
     private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
+    @Mock
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     private TestableStatusBar mStatusBar;
     private FakeMetricsLogger mMetricsLogger;
@@ -252,7 +258,7 @@
                 mDozeScrimController, mock(NotificationShelf.class),
                 mLockscreenUserManager, mCommandQueue, mNotificationPresenter,
                 mock(BubbleController.class), mock(NavigationBarController.class),
-                mock(AutoHideController.class));
+                mock(AutoHideController.class), mKeyguardUpdateMonitor);
         mStatusBar.mContext = mContext;
         mStatusBar.mComponents = mContext.getComponents();
         SystemUIFactory.getInstance().getRootComponent()
@@ -631,6 +637,39 @@
     }
 
     @Test
+    public void testPulseWhileDozing_notifyAuthInterrupt() {
+        HashSet<Integer> reasonsWantingAuth = new HashSet<>(
+                Collections.singletonList(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN));
+        HashSet<Integer> reasonsSkippingAuth = new HashSet<>(
+                Arrays.asList(DozeLog.PULSE_REASON_INTENT,
+                        DozeLog.PULSE_REASON_NOTIFICATION,
+                        DozeLog.PULSE_REASON_SENSOR_SIGMOTION,
+                        DozeLog.REASON_SENSOR_PICKUP,
+                        DozeLog.REASON_SENSOR_DOUBLE_TAP,
+                        DozeLog.PULSE_REASON_SENSOR_LONG_PRESS,
+                        DozeLog.PULSE_REASON_DOCKING,
+                        DozeLog.REASON_SENSOR_WAKE_UP,
+                        DozeLog.REASON_SENSOR_TAP));
+        HashSet<Integer> reasonsThatDontPulse = new HashSet<>(
+                Arrays.asList(DozeLog.REASON_SENSOR_PICKUP,
+                        DozeLog.REASON_SENSOR_DOUBLE_TAP,
+                        DozeLog.REASON_SENSOR_TAP));
+
+        for (int i = 0; i < DozeLog.REASONS; i++) {
+            reset(mKeyguardUpdateMonitor);
+            mStatusBar.mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class), i);
+            if (reasonsWantingAuth.contains(i)) {
+                verify(mKeyguardUpdateMonitor).onAuthInterruptDetected();
+            } else if (reasonsSkippingAuth.contains(i) || reasonsThatDontPulse.contains(i)) {
+                verify(mKeyguardUpdateMonitor, never()).onAuthInterruptDetected();
+            } else {
+                throw new AssertionError("Reason " + i + " isn't specified as wanting or skipping"
+                        + " passive auth. Please consider how this pulse reason should behave.");
+            }
+        }
+    }
+
+    @Test
     public void testSetState_changesIsFullScreenUserSwitcherState() {
         mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
         assertFalse(mStatusBar.isFullScreenUserSwitcherState());
@@ -705,7 +744,8 @@
                 NotificationPresenter notificationPresenter,
                 BubbleController bubbleController,
                 NavigationBarController navBarController,
-                AutoHideController autoHideController) {
+                AutoHideController autoHideController,
+                KeyguardUpdateMonitor keyguardUpdateMonitor) {
             mStatusBarKeyguardViewManager = man;
             mUnlockMethodCache = unlock;
             mKeyguardIndicationController = key;
@@ -738,6 +778,7 @@
             mBubbleController = bubbleController;
             mNavigationBarController = navBarController;
             mAutoHideController = autoHideController;
+            mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         }
 
         private WakefulnessLifecycle createAwakeWakefulnessLifecycle() {
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index aeb4261..fceb656 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -6915,6 +6915,11 @@
     // OS: Q
     MOBILE_NETWORK_RENAME_DIALOG = 1642;
 
+    // ACTION: Settings > Search Bar > Avatar
+    // CATEGORY: SETTINGS
+    // OS: Q
+    ACTION_CLICK_ACCOUNT_AVATAR = 1643;
+
     // ---- End Q Constants, all Q constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index b84736b..2b45b49 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -1888,11 +1888,31 @@
     LABEL_BAD = 2;
   }
 
+  enum UsabilityStatsTriggerType {
+    // Default/Invalid event
+    TYPE_UNKNOWN = 0;
+
+    // There is a data stall from tx failures
+    TYPE_DATA_STALL_BAD_TX = 1;
+
+    // There is a data stall from rx failures
+    TYPE_DATA_STALL_TX_WITHOUT_RX = 2;
+
+    // There is a data stall from both tx and rx failures
+    TYPE_DATA_STALL_BOTH = 3;
+
+    // Firmware generated an alert
+    TYPE_FIRMWARE_ALERT = 4;
+  }
+
   // The current wifi usability state
   optional Label label = 1;
 
   // The list of timestamped wifi usability stats
   repeated WifiUsabilityStatsEntry stats = 2;
+
+  // What event triggered WifiUsabilityStats.
+  optional UsabilityStatsTriggerType trigger_type = 3;
 }
 
 message DeviceMobilityStatePnoScanStats {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 66e9eb3a..d94fc03 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -549,6 +549,11 @@
     // Whether we should use SCHED_FIFO for UI and RenderThreads.
     boolean mUseFifoUiScheduling = false;
 
+    // Use an offload queue for long broadcasts, e.g. BOOT_COMPLETED.
+    // For simplicity, since we statically declare the size of the array of BroadcastQueues,
+    // we still create this new offload queue, but never ever put anything on it.
+    boolean mEnableOffloadQueue;
+
     BroadcastQueue mFgBroadcastQueue;
     BroadcastQueue mBgBroadcastQueue;
     BroadcastQueue mOffloadBroadcastQueue;
@@ -2283,6 +2288,9 @@
         // by default, no "slow" policy in this queue
         offloadConstants.SLOW_TIME = Integer.MAX_VALUE;
 
+        mEnableOffloadQueue = SystemProperties.getBoolean(
+                "persist.device_config.activity_manager_native_boot.offload_queue_enabled", false);
+
         mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
                 "foreground", foreConstants, false);
         mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
@@ -18389,6 +18397,6 @@
     }
 
     private boolean isOnOffloadQueue(int flags) {
-        return ((flags & Intent.FLAG_RECEIVER_OFFLOAD) != 0);
+        return (mEnableOffloadQueue && ((flags & Intent.FLAG_RECEIVER_OFFLOAD) != 0));
     }
 }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 49c4bc4..9cbd065 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1507,7 +1507,7 @@
                 mService.mNativeDebuggingApp = null;
             }
 
-            if (app.info.isCodeIntegrityPreferred()
+            if (app.info.isEmbeddedDexUsed()
                     || (app.info.isPrivilegedApp()
                         && DexManager.isPackageSelectedToRunOob(app.pkgList.mPkgList.keySet()))) {
                 runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 15d66e6..8cc560e 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -719,14 +719,17 @@
             // result back to the client.
             // TODO(b/123378871): Remove when moved.
             if (bundle.getBoolean(BiometricPrompt.KEY_ENABLE_FALLBACK)) {
-                mConfirmDeviceCredentialReceiver = receiver;
-                final KeyguardManager kgm = getContext().getSystemService(KeyguardManager.class);
-                // Use this so we don't need to duplicate logic..
-                final Intent intent = kgm.createConfirmDeviceCredentialIntent(null /* title */,
-                        null /* description */);
-                // Then give it the bundle to do magic behavior..
-                intent.putExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE, bundle);
-                getContext().startActivityAsUser(intent, UserHandle.CURRENT);
+                mHandler.post(() -> {
+                    mConfirmDeviceCredentialReceiver = receiver;
+                    final KeyguardManager kgm = getContext().getSystemService(
+                            KeyguardManager.class);
+                    // Use this so we don't need to duplicate logic..
+                    final Intent intent = kgm.createConfirmDeviceCredentialIntent(null /* title */,
+                            null /* description */);
+                    // Then give it the bundle to do magic behavior..
+                    intent.putExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE, bundle);
+                    getContext().startActivityAsUser(intent, UserHandle.CURRENT);
+                });
                 return;
             }
 
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index c9df86e..80ea1da 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2196,6 +2196,22 @@
             }
         }
 
+        void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
+            if (mDisplayPowerController != null) {
+                synchronized (mSyncRoot) {
+                    mDisplayPowerController.setDisplayWhiteBalanceLoggingEnabled(enabled);
+                }
+            }
+        }
+
+        void setAmbientColorTemperatureOverride(float cct) {
+            if (mDisplayPowerController != null) {
+                synchronized (mSyncRoot) {
+                    mDisplayPowerController.setAmbientColorTemperatureOverride(cct);
+                }
+            }
+        }
+
         private boolean validatePackageName(int uid, String packageName) {
             if (packageName != null) {
                 String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index abbfc7b..04d28ea 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -45,6 +45,12 @@
                 return setAutoBrightnessLoggingEnabled(true);
             case "ab-logging-disable":
                 return setAutoBrightnessLoggingEnabled(false);
+            case "dwb-logging-enable":
+                return setDisplayWhiteBalanceLoggingEnabled(true);
+            case "dwb-logging-disable":
+                return setDisplayWhiteBalanceLoggingEnabled(false);
+            case "dwb-set-cct":
+                return setAmbientColorTemperatureOverride();
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -65,6 +71,12 @@
         pw.println("    Enable auto-brightness logging.");
         pw.println("  ab-logging-disable");
         pw.println("    Disable auto-brightness logging.");
+        pw.println("  dwb-logging-enable");
+        pw.println("    Enable display white-balance logging.");
+        pw.println("  dwb-logging-disable");
+        pw.println("    Disable display white-balance logging.");
+        pw.println("  dwb-set-cct CCT");
+        pw.println("    Sets the ambient color temperature override to CCT (use -1 to disable).");
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
     }
@@ -75,7 +87,7 @@
             getErrPrintWriter().println("Error: no brightness specified");
             return 1;
         }
-        float brightness = -1;
+        float brightness = -1.0f;
         try {
             brightness = Float.parseFloat(brightnessText);
         } catch (NumberFormatException e) {
@@ -84,7 +96,7 @@
             getErrPrintWriter().println("Error: brightness should be a number between 0 and 1");
             return 1;
         }
-        mService.setBrightness((int) brightness * 255);
+        mService.setBrightness((int) (brightness * 255));
         return 0;
     }
 
@@ -97,4 +109,26 @@
         mService.setAutoBrightnessLoggingEnabled(enabled);
         return 0;
     }
+
+    private int setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
+        mService.setDisplayWhiteBalanceLoggingEnabled(enabled);
+        return 0;
+    }
+
+    private int setAmbientColorTemperatureOverride() {
+        String cctText = getNextArg();
+        if (cctText == null) {
+            getErrPrintWriter().println("Error: no cct specified");
+            return 1;
+        }
+        float cct;
+        try {
+            cct = Float.parseFloat(cctText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: cct should be a number");
+            return 1;
+        }
+        mService.setAmbientColorTemperatureOverride(cct);
+        return 0;
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2a8462b..dc5be6a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -52,6 +52,9 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceSettings;
 import com.android.server.policy.WindowManagerPolicy;
 
 import java.io.PrintWriter;
@@ -78,7 +81,8 @@
  * For debugging, you can make the color fade and brightness animations run
  * slower by changing the "animator duration scale" option in Development Settings.
  */
-final class DisplayPowerController implements AutomaticBrightnessController.Callbacks {
+final class DisplayPowerController implements AutomaticBrightnessController.Callbacks,
+        DisplayWhiteBalanceController.Callbacks {
     private static final String TAG = "DisplayPowerController";
     private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked";
     private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked";
@@ -307,6 +311,12 @@
     // Whether or not to skip the initial brightness ramps into STATE_ON.
     private final boolean mSkipScreenOnBrightnessRamp;
 
+    // Display white balance components.
+    @Nullable
+    private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
+    @Nullable
+    private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
+
     // A record of state for skipping brightness ramps.
     private int mSkipRampState = RAMP_STATE_SKIP_NONE;
 
@@ -504,6 +514,20 @@
         mPendingScreenBrightnessSetting = -1;
         mTemporaryAutoBrightnessAdjustment = Float.NaN;
         mPendingAutoBrightnessAdjustment = Float.NaN;
+
+        DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null;
+        DisplayWhiteBalanceController displayWhiteBalanceController = null;
+        try {
+            displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
+            displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler,
+                    mSensorManager, resources);
+            displayWhiteBalanceSettings.setCallbacks(this);
+            displayWhiteBalanceController.setCallbacks(this);
+        } catch (Exception e) {
+            Slog.e(TAG, "failed to set up display white-balance: " + e);
+        }
+        mDisplayWhiteBalanceSettings = displayWhiteBalanceSettings;
+        mDisplayWhiteBalanceController = displayWhiteBalanceController;
     }
 
     /**
@@ -526,6 +550,9 @@
     public void onSwitchUser(@UserIdInt int newUserId) {
         handleSettingsChange(true /* userSwitch */);
         mBrightnessTracker.onSwitchUser(newUserId);
+        if (mDisplayWhiteBalanceSettings != null) {
+            mDisplayWhiteBalanceSettings.onSwitchUser();
+        }
     }
 
     public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
@@ -988,6 +1015,16 @@
 
         }
 
+        // Update display white-balance.
+        if (mDisplayWhiteBalanceController != null) {
+            if (state == Display.STATE_ON && mDisplayWhiteBalanceSettings.isEnabled()) {
+                mDisplayWhiteBalanceController.setEnabled(true);
+                mDisplayWhiteBalanceController.updateScreenColorTemperature();
+            } else {
+                mDisplayWhiteBalanceController.setEnabled(false);
+            }
+        }
+
         // Determine whether the display is ready for use in the newly requested state.
         // Note that we do not wait for the brightness ramp animation to complete before
         // reporting the display is ready because we only need to ensure the screen is in the
@@ -1702,6 +1739,12 @@
             pw.println();
             mBrightnessTracker.dump(pw);
         }
+
+        pw.println();
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.dump(pw);
+            mDisplayWhiteBalanceSettings.dump(pw);
+        }
     }
 
     private static String proximityToString(int state) {
@@ -1848,4 +1891,26 @@
             mAutomaticBrightnessController.setLoggingEnabled(enabled);
         }
     }
+
+    @Override // DisplayWhiteBalanceController.Callbacks
+    public void updateWhiteBalance() {
+        sendUpdatePowerState();
+    }
+
+    void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
+            mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
+        }
+    }
+
+    void setAmbientColorTemperatureOverride(float cct) {
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
+            // The ambient color temperature override is only applied when the ambient color
+            // temperature changes or is updated, so it doesn't necessarily change the screen color
+            // temperature immediately. So, let's make it!
+            sendUpdatePowerState();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/display/utils/History.java b/services/core/java/com/android/server/display/utils/History.java
new file mode 100644
index 0000000..ed171b8
--- /dev/null
+++ b/services/core/java/com/android/server/display/utils/History.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2019 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.display.utils;
+
+import java.time.Clock;
+
+/**
+ * A fixed-size buffer that keeps the most recent values and their times.
+ *
+ * This class is used for logging and debugging purposes only, so there's no way to retrieve the
+ * history other than toString(), and a non-monotonic clock is good enough.
+ */
+public class History {
+
+    private int mSize;
+    private int mCount;
+    private int mStart;
+    private int mEnd;
+
+    private long[] mTimes;
+    private float[] mValues;
+
+    private Clock mClock;
+
+    /**
+     * @param size
+     *      The maximum number of values kept.
+     */
+    public History(int size) {
+        this(size, Clock.systemUTC());
+    }
+
+    /**
+     * @param size
+     *    The maximum number of values kept.
+     * @param clock
+     *    The clock used.
+     */
+    public History(int size, Clock clock) {
+        mSize = size;
+        mCount = 0;
+        mStart = 0;
+        mEnd = 0;
+        mTimes = new long[size];
+        mValues = new float[size];
+        mClock = clock;
+    }
+
+    /**
+     * Add a value.
+     *
+     * @param value
+     *      The value.
+     */
+    public void add(float value) {
+        mTimes[mEnd] = mClock.millis();
+        mValues[mEnd] = value;
+        if (mCount < mSize) {
+            mCount++;
+        } else {
+            mStart = (mStart + 1) % mSize;
+        }
+        mEnd = (mEnd + 1) % mSize;
+    }
+
+    /**
+     * Convert the buffer to string.
+     *
+     * @return The buffer as string.
+     */
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("[");
+        for (int i = 0; i < mCount; i++) {
+            final int index = (mStart + i) % mSize;
+            final long time = mTimes[index];
+            final float value = mValues[index];
+            sb.append(value + " @ " + time);
+            if (i + 1 != mCount) {
+                sb.append(", ");
+            }
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+}
diff --git a/services/core/java/com/android/server/display/utils/RollingBuffer.java b/services/core/java/com/android/server/display/utils/RollingBuffer.java
new file mode 100644
index 0000000..dd5b7ab
--- /dev/null
+++ b/services/core/java/com/android/server/display/utils/RollingBuffer.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2019 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.display.utils;
+
+/**
+ * A buffer that supports adding new values and truncating old ones.
+ */
+public class RollingBuffer {
+
+    private static final int INITIAL_SIZE = 50;
+
+    private int mSize;
+    private int mCount;
+    private int mStart;
+    private int mEnd;
+
+    private long[] mTimes; // Milliseconds
+    private float[] mValues;
+
+    public RollingBuffer() {
+        mSize = INITIAL_SIZE;
+        mTimes = new long[INITIAL_SIZE];
+        mValues = new float[INITIAL_SIZE];
+        clear();
+    }
+
+    /**
+     * Add a value at a given time.
+     *
+     * @param time
+     *      The time (in milliseconds).
+     * @param value
+     *      The value.
+     */
+    public void add(long time, float value) {
+        if (mCount >= mSize) {
+            expandBuffer();
+        }
+        mTimes[mEnd] = time;
+        mValues[mEnd] = value;
+        mEnd = (mEnd + 1) % mSize;
+        mCount++;
+    }
+
+    /**
+     * Get the size of the buffer.
+     *
+     * @return The size of the buffer.
+     */
+    public int size() {
+        return mCount;
+    }
+
+    /**
+     * Return whether the buffer is empty or not.
+     *
+     * @return Whether the buffer is empty or not.
+     */
+    public boolean isEmpty() {
+        return size() == 0;
+    }
+
+    /**
+     * Get a time.
+     *
+     * @param index
+     *      The index of the time.
+     *
+     * @return The time.
+     */
+    public long getTime(int index) {
+        return mTimes[offsetOf(index)];
+    }
+
+    /**
+     * Get a value.
+     *
+     * @param index
+     *      The index of the value.
+     *
+     * @return The value.
+     */
+    public float getValue(int index) {
+        return mValues[offsetOf(index)];
+    }
+
+    /**
+     * Truncate old values.
+     *
+     * @param minTime
+     *      The minimum time (all values older than this time are truncated).
+     */
+    public void truncate(long minTime) {
+        if (isEmpty() || getTime(0) >= minTime) {
+            return;
+        }
+        final int index = getLatestIndexBefore(minTime);
+        mStart = offsetOf(index);
+        mCount -= index;
+        // Remove everything that happened before mTimes[index], but set the index-th value time to
+        // minTime rather than dropping it, as that would've been the value between the minTime and
+        // mTimes[index+1].
+        //
+        // -*---*---|---*---*- => xxxxxxxxx|*--*---*- rather than xxxxxxxxx|???*---*-
+        //      ^       ^                   ^  ^                               ^
+        //      i      i+1                  i i+1                             i+1
+        mTimes[mStart] = minTime;
+    }
+
+    /**
+     * Clears the buffer.
+     */
+    public void clear() {
+        mCount = 0;
+        mStart = 0;
+        mEnd = 0;
+    }
+
+    /**
+     * Convert the buffer to string.
+     *
+     * @return The buffer as string.
+     */
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("[");
+        for (int i = 0; i < mCount; i++) {
+            final int index = offsetOf(i);
+            sb.append(mValues[index] + " @ " + mTimes[index]);
+            if (i + 1 != mCount) {
+                sb.append(", ");
+            }
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    private int offsetOf(int index) {
+        if (index < 0 || index >= mCount) {
+            throw new ArrayIndexOutOfBoundsException("invalid index: " + index + ", mCount= "
+                    + mCount);
+        }
+        return (mStart + index) % mSize;
+    }
+
+    private void expandBuffer() {
+        final int size = mSize * 2;
+        long[] times = new long[size];
+        float[] values = new float[size];
+        System.arraycopy(mTimes, mStart, times, 0, mCount - mStart);
+        System.arraycopy(mTimes, 0, times, mCount - mStart, mStart);
+        System.arraycopy(mValues, mStart, values, 0, mCount - mStart);
+        System.arraycopy(mValues, 0, values, mCount - mStart, mStart);
+        mSize = size;
+        mStart = 0;
+        mEnd = mCount;
+        mTimes = times;
+        mValues = values;
+    }
+
+    private int getLatestIndexBefore(long time) {
+        for (int i = 1; i < mCount; i++) {
+            if (mTimes[offsetOf(i)] > time) {
+                return i - 1;
+            }
+        }
+        return mCount - 1;
+    }
+
+}
diff --git a/services/core/java/com/android/server/display/whitebalance/AmbientFilter.java b/services/core/java/com/android/server/display/whitebalance/AmbientFilter.java
new file mode 100644
index 0000000..532bbed
--- /dev/null
+++ b/services/core/java/com/android/server/display/whitebalance/AmbientFilter.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2019 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.display.whitebalance;
+
+import android.util.Slog;
+
+import com.android.server.display.utils.RollingBuffer;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * The DisplayWhiteBalanceController uses the AmbientFilter to average ambient changes over time,
+ * filter out the noise, and arrive at an estimate of the actual value.
+ *
+ * When the DisplayWhiteBalanceController detects a change in ambient brightness or color
+ * temperature, it passes it to the AmbientFilter, and when it needs the actual ambient value, it
+ * asks it for an estimate.
+ *
+ * Implementations:
+ * - {@link WeightedMovingAverageAmbientFilter}
+ *   A weighted average prioritising recent changes.
+ */
+abstract class AmbientFilter {
+
+    protected static final boolean DEBUG = false; // Enable for verbose logs.
+
+    protected final String mTag;
+    protected boolean mLoggingEnabled;
+
+    // How long ambient value changes are kept and taken into consideration.
+    private final int mHorizon; // Milliseconds
+
+    private final RollingBuffer mBuffer;
+
+    /**
+     * @param tag
+     *      The tag used for dumping and logging.
+     * @param horizon
+     *      How long ambient value changes are kept and taken into consideration.
+     *
+     * @throws IllegalArgumentException
+     *      - horizon is not positive.
+     */
+    AmbientFilter(String tag, int horizon) {
+        validateArguments(horizon);
+        mTag = tag;
+        mLoggingEnabled = false;
+        mHorizon = horizon;
+        mBuffer = new RollingBuffer();
+    }
+
+    /**
+     * Add an ambient value change.
+     *
+     * @param time
+     *      The time.
+     * @param value
+     *      The ambient value.
+     *
+     * @return Whether the method succeeded or not.
+     */
+    public boolean addValue(long time, float value) {
+        if (value < 0.0f) {
+            return false;
+        }
+        truncateOldValues(time);
+        if (mLoggingEnabled) {
+            Slog.d(mTag, "add value: " + value + " @ " + time);
+        }
+        mBuffer.add(time, value);
+        return true;
+    }
+
+    /**
+     * Get an estimate of the actual ambient color temperature.
+     *
+     * @param time
+     *      The time.
+     *
+     * @return An estimate of the actual ambient color temperature.
+     */
+    public float getEstimate(long time) {
+        truncateOldValues(time);
+        final float value = filter(time, mBuffer);
+        if (mLoggingEnabled) {
+            Slog.d(mTag, "get estimate: " + value + " @ " + time);
+        }
+        return value;
+    }
+
+    /**
+     * Clears the filter state.
+     */
+    public void clear() {
+        mBuffer.clear();
+    }
+
+    /**
+     * Enable/disable logging.
+     *
+     * @param loggingEnabled
+     *      Whether logging is on/off.
+     *
+     * @return Whether the method succeeded or not.
+     */
+    public boolean setLoggingEnabled(boolean loggingEnabled) {
+        if (mLoggingEnabled == loggingEnabled) {
+            return false;
+        }
+        mLoggingEnabled = loggingEnabled;
+        return true;
+    }
+
+    /**
+     * Dump the state.
+     *
+     * @param writer
+     *      The PrintWriter used to dump the state.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println("  " + mTag);
+        writer.println("    mLoggingEnabled=" + mLoggingEnabled);
+        writer.println("    mHorizon=" + mHorizon);
+        writer.println("    mBuffer=" + mBuffer);
+    }
+
+    private void validateArguments(int horizon) {
+        if (horizon <= 0) {
+            throw new IllegalArgumentException("horizon must be positive");
+        }
+    }
+
+    private void truncateOldValues(long time) {
+        final long minTime = time - mHorizon;
+        mBuffer.truncate(minTime);
+    }
+
+    protected abstract float filter(long time, RollingBuffer buffer);
+
+    /**
+     * A weighted average prioritising recent changes.
+     */
+    static class WeightedMovingAverageAmbientFilter extends AmbientFilter {
+
+        // How long the latest ambient value change is predicted to last.
+        private static final int PREDICTION_TIME = 100; // Milliseconds
+
+        // Recent changes are prioritised by integrating their duration over y = x + mIntercept
+        // (the higher it is, the less prioritised recent changes are).
+        private final float mIntercept;
+
+        /**
+         * @param tag
+         *      The tag used for dumping and logging.
+         * @param horizon
+         *      How long ambient value changes are kept and taken into consideration.
+         * @param intercept
+         *      Recent changes are prioritised by integrating their duration over y = x + intercept
+         *      (the higher it is, the less prioritised recent changes are).
+         *
+         * @throws IllegalArgumentException
+         *      - horizon is not positive.
+         *      - intercept is NaN or negative.
+         */
+        WeightedMovingAverageAmbientFilter(String tag, int horizon, float intercept) {
+            super(tag, horizon);
+            validateArguments(intercept);
+            mIntercept = intercept;
+        }
+
+        /**
+         * See {@link AmbientFilter#dump base class}.
+         */
+        @Override
+        public void dump(PrintWriter writer) {
+            super.dump(writer);
+            writer.println("    mIntercept=" + mIntercept);
+        }
+
+        // Normalise the times to [t1=0, t2, ..., tN, now + PREDICTION_TIME], so the first change
+        // starts at 0 and the last change is predicted to last a bit, and divide them by 1000 as
+        // milliseconds are high enough to overflow.
+        // The weight of the value from t[i] to t[i+1] is the area under (A.K.A. the integral of)
+        // y = x + mIntercept from t[i] to t[i+1].
+        @Override
+        protected float filter(long time, RollingBuffer buffer) {
+            if (buffer.isEmpty()) {
+                return -1.0f;
+            }
+            float total = 0.0f;
+            float totalWeight = 0.0f;
+            final float[] weights = getWeights(time, buffer);
+            if (DEBUG && mLoggingEnabled) {
+                Slog.v(mTag, "filter: " + buffer + " => " + Arrays.toString(weights));
+            }
+            for (int i = 0; i < weights.length; i++) {
+                final float value = buffer.getValue(i);
+                final float weight = weights[i];
+                total += weight * value;
+                totalWeight += weight;
+            }
+            if (totalWeight == 0.0f) {
+                return buffer.getValue(buffer.size() - 1);
+            }
+            return total / totalWeight;
+        }
+
+        private void validateArguments(float intercept) {
+            if (Float.isNaN(intercept) || intercept < 0.0f) {
+                throw new IllegalArgumentException("intercept must be a non-negative number");
+            }
+        }
+
+        private float[] getWeights(long time, RollingBuffer buffer) {
+            float[] weights = new float[buffer.size()];
+            final long startTime = buffer.getTime(0);
+            float previousTime = 0.0f;
+            for (int i = 1; i < weights.length; i++) {
+                final float currentTime = (buffer.getTime(i) - startTime) / 1000.0f;
+                final float weight = calculateIntegral(previousTime, currentTime);
+                weights[i - 1] = weight;
+                previousTime = currentTime;
+            }
+            final float lastTime = (time + PREDICTION_TIME - startTime) / 1000.0f;
+            final float lastWeight = calculateIntegral(previousTime, lastTime);
+            weights[weights.length - 1] = lastWeight;
+            return weights;
+        }
+
+        private float calculateIntegral(float from, float to) {
+            return antiderivative(to) - antiderivative(from);
+        }
+
+        private float antiderivative(float x) {
+            // f(x) = x + c => F(x) = 1/2 * x^2 + c * x
+            return 0.5f * x * x + mIntercept * x;
+        }
+
+    }
+
+}
diff --git a/services/core/java/com/android/server/display/whitebalance/AmbientSensor.java b/services/core/java/com/android/server/display/whitebalance/AmbientSensor.java
new file mode 100644
index 0000000..1707a62
--- /dev/null
+++ b/services/core/java/com/android/server/display/whitebalance/AmbientSensor.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2019 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.display.whitebalance;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.display.utils.History;
+
+import java.io.PrintWriter;
+
+/**
+ * The DisplayWhiteBalanceController uses the AmbientSensor to detect changes in the ambient
+ * brightness and color temperature.
+ *
+ * The AmbientSensor listens on an actual sensor, derives the ambient brightness or color
+ * temperature from its events, and calls back into the DisplayWhiteBalanceController to report it.
+ */
+abstract class AmbientSensor {
+
+    protected String mTag;
+    protected boolean mLoggingEnabled;
+
+    private final Handler mHandler;
+
+    protected final SensorManager mSensorManager;
+    protected Sensor mSensor;
+
+    private boolean mEnabled;
+
+    private int mRate; // Milliseconds
+
+    // The total events count and the most recent events are kept for debugging purposes.
+    private int mEventsCount;
+    private static final int HISTORY_SIZE = 50;
+    private History mEventsHistory;
+
+    /**
+     * @param tag
+     *      The tag used for dumping and logging.
+     * @param handler
+     *      The handler used to determine which thread to run on.
+     * @param sensorManager
+     *      The sensor manager used to acquire necessary sensors.
+     * @param rate
+     *      The sensor rate.
+     *
+     * @throws IllegalArgumentException
+     *      - rate is not positive.
+     * @throws NullPointerException
+     *      - handler is null;
+     *      - sensorManager is null.
+     * @throws IllegalStateException
+     *      - Cannot find the necessary sensor.
+     */
+    AmbientSensor(String tag, @NonNull Handler handler, @NonNull SensorManager sensorManager,
+            int rate) {
+        validateArguments(handler, sensorManager, rate);
+        mTag = tag;
+        mLoggingEnabled = false;
+        mHandler = handler;
+        mSensorManager = sensorManager;
+        mEnabled = false;
+        mRate = rate;
+        mEventsCount = 0;
+        mEventsHistory = new History(HISTORY_SIZE);
+    }
+
+    /**
+     * Enable/disable the sensor.
+     *
+     * @param enabled
+     *      Whether the sensor should be on/off.
+     *
+     * @return Whether the method succeeded or not.
+     */
+    public boolean setEnabled(boolean enabled) {
+        if (enabled) {
+            return enable();
+        } else {
+            return disable();
+        }
+    }
+
+    /**
+     * Enable/disable logging.
+     *
+     * @param loggingEnabled
+     *      Whether logging should be on/off.
+     *
+     * @return Whether the method succeeded or not.
+     */
+    public boolean setLoggingEnabled(boolean loggingEnabled) {
+        if (mLoggingEnabled == loggingEnabled) {
+            return false;
+        }
+        mLoggingEnabled = loggingEnabled;
+        return true;
+    }
+
+    /**
+     * Dump the state.
+     *
+     * @param writer
+     *      The PrintWriter used to dump the state.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println("  " + mTag);
+        writer.println("    mLoggingEnabled=" + mLoggingEnabled);
+        writer.println("    mHandler=" + mHandler);
+        writer.println("    mSensorManager=" + mSensorManager);
+        writer.println("    mSensor=" + mSensor);
+        writer.println("    mEnabled=" + mEnabled);
+        writer.println("    mRate=" + mRate);
+        writer.println("    mEventsCount=" + mEventsCount);
+        writer.println("    mEventsHistory=" + mEventsHistory);
+    }
+
+
+    private static void validateArguments(Handler handler, SensorManager sensorManager, int rate) {
+        Preconditions.checkNotNull(handler, "handler cannot be null");
+        Preconditions.checkNotNull(sensorManager, "sensorManager cannot be null");
+        if (rate <= 0) {
+            throw new IllegalArgumentException("rate must be positive");
+        }
+    }
+
+    protected abstract void update(float value);
+
+    private boolean enable() {
+        if (mEnabled) {
+            return false;
+        }
+        if (mLoggingEnabled) {
+            Slog.d(mTag, "enabling");
+        }
+        mEnabled = true;
+        startListening();
+        return true;
+    }
+
+    private boolean disable() {
+        if (!mEnabled) {
+            return false;
+        }
+        if (mLoggingEnabled) {
+            Slog.d(mTag, "disabling");
+        }
+        mEnabled = false;
+        mEventsCount = 0;
+        stopListening();
+        return true;
+    }
+
+    private void startListening() {
+        if (mSensorManager == null) {
+            return;
+        }
+        mSensorManager.registerListener(mListener, mSensor, mRate * 1000, mHandler);
+    }
+
+    private void stopListening() {
+        if (mSensorManager == null) {
+            return;
+        }
+        mSensorManager.unregisterListener(mListener);
+    }
+
+    private void handleNewEvent(float value) {
+        // This shouldn't really happen, except for the race condition where the sensor is disabled
+        // with an event already in the handler queue, in which case we discard that event.
+        if (!mEnabled) {
+            return;
+        }
+        if (mLoggingEnabled) {
+            Slog.d(mTag, "handle new event: " + value);
+        }
+        mEventsCount++;
+        mEventsHistory.add(value);
+        update(value);
+    }
+
+    private SensorEventListener mListener = new SensorEventListener() {
+
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            final float value = event.values[0];
+            handleNewEvent(value);
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            // Not used.
+        }
+
+    };
+
+    /**
+     * A sensor that reports the ambient brightness.
+     */
+    static class AmbientBrightnessSensor extends AmbientSensor {
+
+        private static final String TAG = "AmbientBrightnessSensor";
+
+        // To decouple the DisplayWhiteBalanceController from the AmbientBrightnessSensor, the
+        // DWBC implements Callbacks and passes itself to the ABS so it can call back into it
+        // without knowing about it.
+        @Nullable
+        private Callbacks mCallbacks;
+
+        /**
+         * @param handler
+         *      The handler used to determine which thread to run on.
+         * @param sensorManager
+         *      The sensor manager used to acquire necessary sensors.
+         * @param rate
+         *      The sensor rate.
+         *
+         * @throws IllegalArgumentException
+         *      - rate is not positive.
+         * @throws NullPointerException
+         *      - handler is null;
+         *      - sensorManager is null.
+         * @throws IllegalStateException
+         *      - Cannot find the light sensor.
+         */
+        AmbientBrightnessSensor(@NonNull Handler handler, @NonNull SensorManager sensorManager,
+                int rate) {
+            super(TAG, handler, sensorManager, rate);
+            mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
+            if (mSensor == null) {
+                throw new IllegalStateException("cannot find light sensor");
+            }
+            mCallbacks = null;
+        }
+
+        /**
+         * Set an object to call back to when the ambient brightness changes.
+         *
+         * @param callbacks
+         *      The object to call back to.
+         *
+         * @return Whether the method succeeded or not.
+         */
+        public boolean setCallbacks(Callbacks callbacks) {
+            if (mCallbacks == callbacks) {
+                return false;
+            }
+            mCallbacks = callbacks;
+            return true;
+        }
+
+        /**
+         * See {@link AmbientSensor#dump base class}.
+         */
+        @Override
+        public void dump(PrintWriter writer) {
+            super.dump(writer);
+            writer.println("    mCallbacks=" + mCallbacks);
+        }
+
+        interface Callbacks {
+            void onAmbientBrightnessChanged(float value);
+        }
+
+        @Override
+        protected void update(float value) {
+            if (mCallbacks != null) {
+                mCallbacks.onAmbientBrightnessChanged(value);
+            }
+        }
+
+    }
+
+    /**
+     * A sensor that reports the ambient color temperature.
+     */
+    static class AmbientColorTemperatureSensor extends AmbientSensor {
+
+        private static final String TAG = "AmbientColorTemperatureSensor";
+
+        // To decouple the DisplayWhiteBalanceController from the
+        // AmbientColorTemperatureSensor, the DWBC implements Callbacks and passes itself to the
+        // ACTS so it can call back into it without knowing about it.
+        @Nullable
+        private Callbacks mCallbacks;
+
+        /**
+         * @param handler
+         *      The handler used to determine which thread to run on.
+         * @param sensorManager
+         *      The sensor manager used to acquire necessary sensors.
+         * @param name
+         *      The color sensor name.
+         * @param rate
+         *      The sensor rate.
+         *
+         * @throws IllegalArgumentException
+         *      - rate is not positive.
+         * @throws NullPointerException
+         *      - handler is null;
+         *      - sensorManager is null.
+         * @throws IllegalStateException
+         *      - Cannot find the color sensor.
+         */
+        AmbientColorTemperatureSensor(@NonNull Handler handler,
+                @NonNull SensorManager sensorManager, String name, int rate) {
+            super(TAG, handler, sensorManager, rate);
+            mSensor = null;
+            for (Sensor sensor : mSensorManager.getSensorList(Sensor.TYPE_ALL)) {
+                if (sensor.getStringType().equals(name)) {
+                    mSensor = sensor;
+                    break;
+                }
+            }
+            if (mSensor == null) {
+                throw new IllegalStateException("cannot find sensor " + name);
+            }
+            mCallbacks = null;
+        }
+
+        /**
+         * Set an object to call back to when the ambient color temperature changes.
+         *
+         * @param callbacks
+         *      The object to call back to.
+         *
+         * @return Whether the method succeeded or not.
+         */
+        public boolean setCallbacks(Callbacks callbacks) {
+            if (mCallbacks == callbacks) {
+                return false;
+            }
+            mCallbacks = callbacks;
+            return true;
+        }
+
+        /**
+         * See {@link AmbientSensor#dump base class}.
+         */
+        @Override
+        public void dump(PrintWriter writer) {
+            super.dump(writer);
+            writer.println("    mCallbacks=" + mCallbacks);
+        }
+
+        interface Callbacks {
+            void onAmbientColorTemperatureChanged(float value);
+        }
+
+        @Override
+        protected void update(float value) {
+            if (mCallbacks != null) {
+                mCallbacks.onAmbientColorTemperatureChanged(value);
+            }
+        }
+
+    }
+
+}
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
new file mode 100644
index 0000000..7ae00af
--- /dev/null
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2019 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.display.whitebalance;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
+import com.android.server.display.ColorDisplayService.ColorDisplayServiceInternal;
+import com.android.server.display.utils.History;
+
+import java.io.PrintWriter;
+
+/**
+ * The DisplayWhiteBalanceController drives display white-balance (automatically correcting the
+ * screen color temperature depending on the ambient color temperature).
+ *
+ * The DisplayWhiteBalanceController:
+ * - Uses the AmbientColorTemperatureSensor to detect changes in the ambient color temperature;
+ * - Uses the AmbientColorTemperatureFilter to average these changes over time, filter out the
+ *   noise, and arrive at an estimate of the actual ambient color temperature;
+ * - Uses the DisplayWhiteBalanceThrottler to decide whether the screen color tempearture should be
+ *   updated, suppressing changes that are too frequent or too minor.
+ */
+public class DisplayWhiteBalanceController implements
+        AmbientSensor.AmbientBrightnessSensor.Callbacks,
+        AmbientSensor.AmbientColorTemperatureSensor.Callbacks {
+
+    protected static final String TAG = "DisplayWhiteBalanceController";
+    protected boolean mLoggingEnabled;
+
+    private boolean mEnabled;
+
+    // To decouple the DisplayPowerController from the DisplayWhiteBalanceController, the DPC
+    // implements Callbacks and passes itself to the DWBC so it can call back into it without
+    // knowing about it.
+    private Callbacks mCallbacks;
+
+    private AmbientSensor.AmbientBrightnessSensor mBrightnessSensor;
+    private AmbientFilter mBrightnessFilter;
+    private AmbientSensor.AmbientColorTemperatureSensor mColorTemperatureSensor;
+    private AmbientFilter mColorTemperatureFilter;
+    private DisplayWhiteBalanceThrottler mThrottler;
+
+    // When the brightness drops below a certain threshold, it affects the color temperature
+    // accuracy, so we fall back to a fixed ambient color temperature.
+    private final float mLowLightAmbientBrightnessThreshold;
+    private final float mLowLightAmbientColorTemperature;
+
+    private float mAmbientColorTemperature;
+    private float mPendingAmbientColorTemperature;
+    private float mLastAmbientColorTemperature;
+
+    private ColorDisplayServiceInternal mColorDisplayServiceInternal;
+
+    // The most recent ambient color temperature values are kept for debugging purposes.
+    private static final int HISTORY_SIZE = 50;
+    private History mAmbientColorTemperatureHistory;
+
+    // Override the ambient color temperature for debugging purposes.
+    private float mAmbientColorTemperatureOverride;
+
+    /**
+     * @param brightnessSensor
+     *      The sensor used to detect changes in the ambient brightness.
+     * @param brightnessFilter
+     *      The filter used to avergae ambient brightness changes over time, filter out the noise
+     *      and arrive at an estimate of the actual ambient brightness.
+     * @param colorTemperatureSensor
+     *      The sensor used to detect changes in the ambient color temperature.
+     * @param colorTemperatureFilter
+     *      The filter used to average ambient color temperature changes over time, filter out the
+     *      noise and arrive at an estimate of the actual ambient color temperature.
+     * @param throttler
+     *      The throttler used to determine whether the new screen color temperature should be
+     *      updated or not.
+     * @param lowLightAmbientBrightnessThreshold
+     *      The ambient brightness threshold beneath which we fall back to a fixed ambient color
+     *      temperature.
+     * @param lowLightAmbientColorTemperature
+     *      The ambient color temperature to which we fall back when the ambient brightness drops
+     *      beneath a certain threshold.
+     *
+     * @throws NullPointerException
+     *      - brightnessSensor is null;
+     *      - brightnessFilter is null;
+     *      - colorTemperatureSensor is null;
+     *      - colorTemperatureFilter is null;
+     *      - throttler is null.
+     */
+    public DisplayWhiteBalanceController(
+            @NonNull AmbientSensor.AmbientBrightnessSensor brightnessSensor,
+            @NonNull AmbientFilter brightnessFilter,
+            @NonNull AmbientSensor.AmbientColorTemperatureSensor colorTemperatureSensor,
+            @NonNull AmbientFilter colorTemperatureFilter,
+            @NonNull DisplayWhiteBalanceThrottler throttler,
+            float lowLightAmbientBrightnessThreshold, float lowLightAmbientColorTemperature) {
+        validateArguments(brightnessSensor, brightnessFilter, colorTemperatureSensor,
+                colorTemperatureFilter, throttler);
+        mLoggingEnabled = false;
+        mEnabled = false;
+        mCallbacks = null;
+        mBrightnessSensor = brightnessSensor;
+        mBrightnessFilter = brightnessFilter;
+        mColorTemperatureSensor = colorTemperatureSensor;
+        mColorTemperatureFilter = colorTemperatureFilter;
+        mThrottler = throttler;
+        mLowLightAmbientBrightnessThreshold = lowLightAmbientBrightnessThreshold;
+        mLowLightAmbientColorTemperature = lowLightAmbientColorTemperature;
+        mAmbientColorTemperature = -1.0f;
+        mPendingAmbientColorTemperature = -1.0f;
+        mLastAmbientColorTemperature = -1.0f;
+        mAmbientColorTemperatureHistory = new History(HISTORY_SIZE);
+        mAmbientColorTemperatureOverride = -1.0f;
+        mColorDisplayServiceInternal = LocalServices.getService(ColorDisplayServiceInternal.class);
+    }
+
+    /**
+     * Enable/disable the controller.
+     *
+     * @param enabled
+     *      Whether the controller should be on/off.
+     *
+     * @return Whether the method succeeded or not.
+     */
+    public boolean setEnabled(boolean enabled) {
+        if (enabled) {
+            return enable();
+        } else {
+            return disable();
+        }
+    }
+
+    /**
+     * Set an object to call back to when the screen color temperature should be updated.
+     *
+     * @param callbacks
+     *      The object to call back to.
+     *
+     * @return Whether the method succeeded or not.
+     */
+    public boolean setCallbacks(Callbacks callbacks) {
+        if (mCallbacks == callbacks) {
+            return false;
+        }
+        mCallbacks = callbacks;
+        return true;
+    }
+
+    /**
+     * Enable/disable logging.
+     *
+     * @param loggingEnabled
+     *      Whether logging should be on/off.
+     *
+     * @return Whether the method succeeded or not.
+     */
+    public boolean setLoggingEnabled(boolean loggingEnabled) {
+        if (mLoggingEnabled == loggingEnabled) {
+            return false;
+        }
+        mLoggingEnabled = loggingEnabled;
+        mBrightnessSensor.setLoggingEnabled(loggingEnabled);
+        mBrightnessFilter.setLoggingEnabled(loggingEnabled);
+        mColorTemperatureSensor.setLoggingEnabled(loggingEnabled);
+        mColorTemperatureFilter.setLoggingEnabled(loggingEnabled);
+        mThrottler.setLoggingEnabled(loggingEnabled);
+        return true;
+    }
+
+    /**
+     * Set the ambient color temperature override.
+     *
+     * This is only applied when the ambient color temperature changes or is updated (in which case
+     * it overrides the ambient color temperature estimate); in other words, it doesn't necessarily
+     * change the screen color temperature immediately.
+     *
+     * @param ambientColorTemperatureOverride
+     *      The ambient color temperature override.
+     *
+     * @return Whether the method succeeded or not.
+     */
+    public boolean setAmbientColorTemperatureOverride(float ambientColorTemperatureOverride) {
+        if (mAmbientColorTemperatureOverride == ambientColorTemperatureOverride) {
+            return false;
+        }
+        mAmbientColorTemperatureOverride = ambientColorTemperatureOverride;
+        return true;
+    }
+
+    /**
+     * Dump the state.
+     *
+     * @param writer
+     *      The writer used to dump the state.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println("DisplayWhiteBalanceController");
+        writer.println("  mLoggingEnabled=" + mLoggingEnabled);
+        writer.println("  mEnabled=" + mEnabled);
+        writer.println("  mCallbacks=" + mCallbacks);
+        mBrightnessSensor.dump(writer);
+        mBrightnessFilter.dump(writer);
+        mColorTemperatureSensor.dump(writer);
+        mColorTemperatureFilter.dump(writer);
+        mThrottler.dump(writer);
+        writer.println("  mLowLightAmbientBrightnessThreshold="
+                + mLowLightAmbientBrightnessThreshold);
+        writer.println("  mLowLightAmbientColorTemperature=" + mLowLightAmbientColorTemperature);
+        writer.println("  mAmbientColorTemperature=" + mAmbientColorTemperature);
+        writer.println("  mPendingAmbientColorTemperature=" + mPendingAmbientColorTemperature);
+        writer.println("  mLastAmbientColorTemperature=" + mLastAmbientColorTemperature);
+        writer.println("  mAmbientColorTemperatureHistory=" + mAmbientColorTemperatureHistory);
+        writer.println("  mAmbientColorTemperatureOverride=" + mAmbientColorTemperatureOverride);
+    }
+
+    @Override // AmbientSensor.AmbientBrightnessSensor.Callbacks
+    public void onAmbientBrightnessChanged(float value) {
+        final long time = System.currentTimeMillis();
+        mBrightnessFilter.addValue(time, value);
+        updateAmbientColorTemperature();
+    }
+
+    @Override // AmbientSensor.AmbientColorTemperatureSensor.Callbacks
+    public void onAmbientColorTemperatureChanged(float value) {
+        final long time = System.currentTimeMillis();
+        mColorTemperatureFilter.addValue(time, value);
+        updateAmbientColorTemperature();
+    }
+
+    /**
+     * Updates the ambient color temperature.
+     */
+    public void updateAmbientColorTemperature() {
+        final long time = System.currentTimeMillis();
+        float ambientColorTemperature = mColorTemperatureFilter.getEstimate(time);
+
+        final float ambientBrightness = mBrightnessFilter.getEstimate(time);
+        if (ambientBrightness < mLowLightAmbientBrightnessThreshold) {
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "low light ambient brightness: " + ambientBrightness + " < "
+                        + mLowLightAmbientBrightnessThreshold
+                        + ", falling back to fixed ambient color temperature: "
+                        + ambientColorTemperature + " => " + mLowLightAmbientColorTemperature);
+            }
+            ambientColorTemperature = mLowLightAmbientColorTemperature;
+        }
+
+        if (mAmbientColorTemperatureOverride != -1.0f) {
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "override ambient color temperature: " + ambientColorTemperature
+                        + " => " + mAmbientColorTemperatureOverride);
+            }
+            ambientColorTemperature = mAmbientColorTemperatureOverride;
+        }
+
+        // When the screen color temperature needs to be updated, we call DisplayPowerController to
+        // call our updateColorTemperature. The reason we don't call it directly is that we want
+        // all changes to the system to happen in a predictable order in DPC's main loop
+        // (updatePowerState).
+        if (ambientColorTemperature == -1.0f || mThrottler.throttle(ambientColorTemperature)) {
+            return;
+        }
+
+        if (mLoggingEnabled) {
+            Slog.d(TAG, "pending ambient color temperature: " + ambientColorTemperature);
+        }
+        mPendingAmbientColorTemperature = ambientColorTemperature;
+        if (mCallbacks != null) {
+            mCallbacks.updateWhiteBalance();
+        }
+    }
+
+    /**
+     * Updates the screen color temperature.
+     */
+    public void updateScreenColorTemperature() {
+        float ambientColorTemperature = -1.0f;
+
+        // If both the pending and the current ambient color temperatures are -1, it means the DWBC
+        // was just enabled, and we use the last ambient color temperature until new sensor events
+        // give us a better estimate.
+        if (mAmbientColorTemperature == -1.0f && mPendingAmbientColorTemperature == -1.0f) {
+            ambientColorTemperature = mLastAmbientColorTemperature;
+        }
+
+        // Otherwise, we use the pending ambient color temperature, but only if it's non-trivial
+        // and different than the current one.
+        if (mPendingAmbientColorTemperature != -1.0f
+                && mPendingAmbientColorTemperature != mAmbientColorTemperature) {
+            ambientColorTemperature = mPendingAmbientColorTemperature;
+        }
+
+        if (ambientColorTemperature == -1.0f) {
+            return;
+        }
+
+        mAmbientColorTemperature = ambientColorTemperature;
+        if (mLoggingEnabled) {
+            Slog.d(TAG, "ambient color temperature: " + mAmbientColorTemperature);
+        }
+        mPendingAmbientColorTemperature = -1.0f;
+        mAmbientColorTemperatureHistory.add(mAmbientColorTemperature);
+        mColorDisplayServiceInternal.setDisplayWhiteBalanceColorTemperature(
+                (int) mAmbientColorTemperature);
+        mLastAmbientColorTemperature = mAmbientColorTemperature;
+    }
+
+    /**
+     * The DisplayWhiteBalanceController decouples itself from its parent (DisplayPowerController)
+     * by providing this interface to implement (and a method to set its callbacks object), and
+     * calling these methods.
+     */
+    public interface Callbacks {
+
+        /**
+         * Called whenever the display white-balance state has changed.
+         *
+         * Usually, this means the estimated ambient color temperature has changed enough, and the
+         * screen color temperature should be updated; but it is also called by
+         */
+        void updateWhiteBalance();
+    }
+
+    private void validateArguments(AmbientSensor.AmbientBrightnessSensor brightnessSensor,
+            AmbientFilter brightnessFilter,
+            AmbientSensor.AmbientColorTemperatureSensor colorTemperatureSensor,
+            AmbientFilter colorTemperatureFilter,
+            DisplayWhiteBalanceThrottler throttler) {
+        Preconditions.checkNotNull(brightnessSensor, "brightnessSensor must not be null");
+        Preconditions.checkNotNull(brightnessFilter, "brightnessFilter must not be null");
+        Preconditions.checkNotNull(colorTemperatureSensor,
+                "colorTemperatureSensor must not be null");
+        Preconditions.checkNotNull(colorTemperatureFilter,
+                "colorTemperatureFilter must not be null");
+        Preconditions.checkNotNull(throttler, "throttler cannot be null");
+    }
+
+    private boolean enable() {
+        if (mEnabled) {
+            return false;
+        }
+        if (mLoggingEnabled) {
+            Slog.d(TAG, "enabling");
+        }
+        mEnabled = true;
+        mBrightnessSensor.setEnabled(true);
+        mColorTemperatureSensor.setEnabled(true);
+        return true;
+    }
+
+    private boolean disable() {
+        if (!mEnabled) {
+            return false;
+        }
+        if (mLoggingEnabled) {
+            Slog.d(TAG, "disabling");
+        }
+        mEnabled = false;
+        mBrightnessSensor.setEnabled(false);
+        mBrightnessFilter.clear();
+        mColorTemperatureSensor.setEnabled(false);
+        mColorTemperatureFilter.clear();
+        mThrottler.clear();
+        mAmbientColorTemperature = -1.0f;
+        mPendingAmbientColorTemperature = -1.0f;
+        return true;
+    }
+
+}
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
new file mode 100644
index 0000000..fd78ddb
--- /dev/null
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2019 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.display.whitebalance;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.util.TypedValue;
+
+/**
+ * The DisplayWhiteBalanceFactory creates and configures an DisplayWhiteBalanceController.
+ */
+public class DisplayWhiteBalanceFactory {
+
+    private static final String BRIGHTNESS_FILTER_TAG = "AmbientBrightnessFilter";
+    private static final String COLOR_TEMPERATURE_FILTER_TAG = "AmbientColorTemperatureFilter";
+
+    /**
+     * Create and configure an DisplayWhiteBalanceController.
+     *
+     * @param handler
+     *      The handler used to determine which thread to run on.
+     * @param sensorManager
+     *      The sensor manager used to acquire necessary sensors.
+     * @param resources
+     *      The resources used to configure the various components.
+     *
+     * @return An DisplayWhiteBalanceController.
+     *
+     * @throws NullPointerException
+     *      - handler is null;
+     *      - sensorManager is null.
+     * @throws Resources.NotFoundException
+     *      - Configurations are missing.
+     * @throws IllegalArgumentException
+     *      - Configurations are invalid.
+     * @throws IllegalStateException
+     *      - Cannot find the necessary sensors.
+     */
+    public static DisplayWhiteBalanceController create(Handler handler,
+            SensorManager sensorManager, Resources resources) {
+        final AmbientSensor.AmbientBrightnessSensor brightnessSensor =
+                createBrightnessSensor(handler, sensorManager, resources);
+        final AmbientFilter brightnessFilter = createBrightnessFilter(resources);
+        final AmbientSensor.AmbientColorTemperatureSensor colorTemperatureSensor =
+                createColorTemperatureSensor(handler, sensorManager, resources);
+        final AmbientFilter colorTemperatureFilter = createColorTemperatureFilter(resources);
+        final DisplayWhiteBalanceThrottler throttler = createThrottler(resources);
+        final float lowLightAmbientBrightnessThreshold = getFloat(resources,
+                com.android.internal.R.dimen
+                .config_displayWhiteBalanceLowLightAmbientBrightnessThreshold);
+        final float lowLightAmbientColorTemperature = getFloat(resources,
+                com.android.internal.R.dimen
+                .config_displayWhiteBalanceLowLightAmbientColorTemperature);
+        final DisplayWhiteBalanceController controller = new DisplayWhiteBalanceController(
+                brightnessSensor, brightnessFilter, colorTemperatureSensor, colorTemperatureFilter,
+                throttler, lowLightAmbientBrightnessThreshold, lowLightAmbientColorTemperature);
+        brightnessSensor.setCallbacks(controller);
+        colorTemperatureSensor.setCallbacks(controller);
+        return controller;
+    }
+
+    // Instantiation is disabled.
+    private DisplayWhiteBalanceFactory() { }
+
+    private static AmbientSensor.AmbientBrightnessSensor createBrightnessSensor(Handler handler,
+            SensorManager sensorManager, Resources resources) {
+        final int rate = resources.getInteger(
+                com.android.internal.R.integer.config_displayWhiteBalanceBrightnessSensorRate);
+        return new AmbientSensor.AmbientBrightnessSensor(handler, sensorManager, rate);
+    }
+
+    private static AmbientFilter createBrightnessFilter(Resources resources) {
+        final int horizon = resources.getInteger(
+                com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon);
+        final float intercept = getFloat(resources,
+                com.android.internal.R.dimen.config_displayWhiteBalanceBrightnessFilterIntercept);
+        if (!Float.isNaN(intercept)) {
+            return new AmbientFilter.WeightedMovingAverageAmbientFilter(
+                    BRIGHTNESS_FILTER_TAG, horizon, intercept);
+        }
+        throw new IllegalArgumentException("missing configurations: "
+                + "expected config_displayWhiteBalanceBrightnessFilterIntercept");
+    }
+
+
+    private static AmbientSensor.AmbientColorTemperatureSensor createColorTemperatureSensor(
+            Handler handler, SensorManager sensorManager, Resources resources) {
+        final String name = resources.getString(
+                com.android.internal.R.string
+                .config_displayWhiteBalanceColorTemperatureSensorName);
+        final int rate = resources.getInteger(
+                com.android.internal.R.integer
+                .config_displayWhiteBalanceColorTemperatureSensorRate);
+        return new AmbientSensor.AmbientColorTemperatureSensor(handler, sensorManager, name, rate);
+    }
+
+    private static AmbientFilter createColorTemperatureFilter(Resources resources) {
+        final int horizon = resources.getInteger(
+                com.android.internal.R.integer
+                .config_displayWhiteBalanceColorTemperatureFilterHorizon);
+        final float intercept = getFloat(resources,
+                com.android.internal.R.dimen
+                .config_displayWhiteBalanceColorTemperatureFilterIntercept);
+        if (!Float.isNaN(intercept)) {
+            return new AmbientFilter.WeightedMovingAverageAmbientFilter(
+                    COLOR_TEMPERATURE_FILTER_TAG, horizon, intercept);
+        }
+        throw new IllegalArgumentException("missing configurations: "
+                + "expected config_displayWhiteBalanceColorTemperatureFilterIntercept");
+    }
+
+    private static DisplayWhiteBalanceThrottler createThrottler(Resources resources) {
+        final int increaseDebounce = resources.getInteger(
+                com.android.internal.R.integer.config_displayWhiteBalanceDecreaseDebounce);
+        final int decreaseDebounce = resources.getInteger(
+                com.android.internal.R.integer.config_displayWhiteBalanceIncreaseDebounce);
+        final float[] baseThresholds = getFloatArray(resources,
+                com.android.internal.R.array.config_displayWhiteBalanceBaseThresholds);
+        final float[] increaseThresholds = getFloatArray(resources,
+                com.android.internal.R.array.config_displayWhiteBalanceIncreaseThresholds);
+        final float[] decreaseThresholds = getFloatArray(resources,
+                com.android.internal.R.array.config_displayWhiteBalanceDecreaseThresholds);
+        return new DisplayWhiteBalanceThrottler(increaseDebounce, decreaseDebounce, baseThresholds,
+                increaseThresholds, decreaseThresholds);
+    }
+
+    private static float getFloat(Resources resources, int id) {
+        TypedValue value = new TypedValue();
+        resources.getValue(id, value, true /* resolveRefs */);
+        if (value.type != TypedValue.TYPE_FLOAT) {
+            return Float.NaN;
+        }
+        return value.getFloat();
+    }
+
+    private static float[] getFloatArray(Resources resources, int id) {
+        TypedArray array = resources.obtainTypedArray(id);
+        try {
+            if (array.length() == 0) {
+                return null;
+            }
+            float[] values = new float[array.length()];
+            for (int i = 0; i < values.length; i++) {
+                values[i] = array.getFloat(i, Float.NaN);
+                if (Float.isNaN(values[i])) {
+                    return null;
+                }
+            }
+            return values;
+        } finally {
+            array.recycle();
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceSettings.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceSettings.java
new file mode 100644
index 0000000..a53e91c
--- /dev/null
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceSettings.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2019 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.display.whitebalance;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings.Secure;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
+import com.android.server.display.ColorDisplayService;
+import com.android.server.display.ColorDisplayService.ColorDisplayServiceInternal;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController.Callbacks;
+
+import java.io.PrintWriter;
+
+/**
+ * The DisplayWhiteBalanceSettings holds the state of all the settings related to
+ * display white-balance, and can be used to decide whether to enable the
+ * DisplayWhiteBalanceController.
+ */
+public class DisplayWhiteBalanceSettings implements
+        ColorDisplayService.DisplayWhiteBalanceListener {
+
+    protected static final String TAG = "DisplayWhiteBalanceSettings";
+    protected boolean mLoggingEnabled;
+
+    private static final String SETTING_URI = Secure.DISPLAY_WHITE_BALANCE_ENABLED;
+    private static final int SETTING_DEFAULT = 0;
+    private static final int SETTING_ENABLED = 1;
+
+    private static final int MSG_SET_ACTIVE = 1;
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final SettingsObserver mSettingsObserver;
+
+    // To decouple the DisplayPowerController from the DisplayWhiteBalanceSettings, the DPC
+    // implements Callbacks and passes itself to the DWBS so it can call back into it without
+    // knowing about it.
+    private Callbacks mCallbacks;
+
+    private int mSetting;
+    private boolean mActive;
+
+    /**
+     * @param context
+     *      The context in which display white-balance is used.
+     * @param handler
+     *      The handler used to determine which thread to run on.
+     *
+     * @throws NullPointerException
+     *      - context is null;
+     *      - handler is null.
+     */
+    public DisplayWhiteBalanceSettings(@NonNull Context context, @NonNull Handler handler) {
+        validateArguments(context, handler);
+        mLoggingEnabled = false;
+        mContext = context;
+        mHandler = new DisplayWhiteBalanceSettingsHandler(handler.getLooper());
+        mSettingsObserver = new SettingsObserver(mHandler);
+        mSetting = getSetting();
+        mActive = false;
+        mCallbacks = null;
+
+        mContext.getContentResolver().registerContentObserver(
+                Secure.getUriFor(SETTING_URI), false /* notifyForDescendants */, mSettingsObserver,
+                UserHandle.USER_ALL);
+
+        ColorDisplayServiceInternal cds =
+                LocalServices.getService(ColorDisplayServiceInternal.class);
+        cds.setDisplayWhiteBalanceListener(this);
+    }
+
+    /**
+     * Set an object to call back to when the display white balance state should be updated.
+     *
+     * @param callbacks
+     *      The object to call back to.
+     *
+     * @return Whether the method suceeded or not.
+     */
+    public boolean setCallbacks(Callbacks callbacks) {
+        if (mCallbacks == callbacks) {
+            return false;
+        }
+        mCallbacks = callbacks;
+        return true;
+    }
+
+    /**
+     * Enable/disable logging.
+     *
+     * @param loggingEnabled
+     *      Whether logging should be on/off.
+     *
+     * @return Whether the method succeeded or not.
+     */
+    public boolean setLoggingEnabled(boolean loggingEnabled) {
+        if (mLoggingEnabled == loggingEnabled) {
+            return false;
+        }
+        mLoggingEnabled = loggingEnabled;
+        return true;
+    }
+
+    /**
+     * Returns whether display white-balance is enabled.
+     *
+     * @return Whether display white-balance is enabled.
+     */
+    public boolean isEnabled() {
+        return (mSetting == SETTING_ENABLED) && mActive;
+    }
+
+    /**
+     * Re-evaluate state after switching to a new user.
+     */
+    public void onSwitchUser() {
+        handleSettingChange();
+    }
+
+    /**
+     * Dump the state.
+     *
+     * @param writer
+     *      The writer used to dump the state.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println("DisplayWhiteBalanceSettings");
+        writer.println("  mLoggingEnabled=" + mLoggingEnabled);
+        writer.println("  mContext=" + mContext);
+        writer.println("  mHandler=" + mHandler);
+        writer.println("  mSettingsObserver=" + mSettingsObserver);
+        writer.println("  mSetting=" + mSetting);
+        writer.println("  mActive=" + mActive);
+        writer.println("  mCallbacks=" + mCallbacks);
+    }
+
+    @Override
+    public void onDisplayWhiteBalanceStatusChanged(boolean active) {
+        Message msg = mHandler.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, 0);
+        msg.sendToTarget();
+    }
+
+    private void validateArguments(Context context, Handler handler) {
+        Preconditions.checkNotNull(context, "context must not be null");
+        Preconditions.checkNotNull(handler, "handler must not be null");
+    }
+
+    private int getSetting() {
+        return Secure.getIntForUser(mContext.getContentResolver(), SETTING_URI, SETTING_DEFAULT,
+                UserHandle.USER_CURRENT);
+    }
+
+    private void handleSettingChange() {
+        final int setting = getSetting();
+        if (mSetting == setting) {
+            return;
+        }
+        if (mLoggingEnabled) {
+            Slog.d(TAG, "Setting: " + setting);
+        }
+        mSetting = setting;
+        if (mCallbacks != null) {
+            mCallbacks.updateWhiteBalance();
+        }
+    }
+
+    private void setActive(boolean active) {
+        if (mActive == active) {
+            return;
+        }
+        if (mLoggingEnabled) {
+            Slog.d(TAG, "Active: " + active);
+        }
+        mActive = active;
+        if (mCallbacks != null) {
+            mCallbacks.updateWhiteBalance();
+        }
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            handleSettingChange();
+        }
+    }
+
+    private final class DisplayWhiteBalanceSettingsHandler extends Handler {
+        DisplayWhiteBalanceSettingsHandler(Looper looper) {
+            super(looper, null, true /* async */);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SET_ACTIVE:
+                    setActive(msg.arg1 != 0);
+                    break;
+            }
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceThrottler.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceThrottler.java
new file mode 100644
index 0000000..c1f0e98
--- /dev/null
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceThrottler.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2019 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.display.whitebalance;
+
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * The DisplayWhiteBalanceController uses the DisplayWhiteBalanceThrottler to decide whether the
+ * screen color temperature should be updated, suppressing changes that are too frequent or too
+ * minor.
+ */
+class DisplayWhiteBalanceThrottler {
+
+    protected static final String TAG = "DisplayWhiteBalanceThrottler";
+    protected boolean mLoggingEnabled;
+
+    private int mIncreaseDebounce;  // Milliseconds
+    private int mDecreaseDebounce;  // Milliseconds
+    private long mLastTime;         // Milliseconds
+
+    private float[] mBaseThresholds;
+    private float[] mIncreaseThresholds;
+    private float[] mDecreaseThresholds;
+    private float mIncreaseThreshold;
+    private float mDecreaseThreshold;
+    private float mLastValue;
+
+    /**
+     * @param increaseDebounce
+     *      The debounce time for increasing (throttled if {@code time < lastTime + debounce}).
+     * @param decreaseDebounce
+     *      The debounce time for decreasing (throttled if {@code time < lastTime + debounce}).
+     * @param baseThresholds
+     *      The ambient color temperature values used to determine the threshold as the
+     *      corresponding value in increaseThresholds/decreaseThresholds.
+     * @param increaseThresholds
+     *      The increase threshold values (throttled if {@code value < value * (1 + threshold)}).
+     * @param decreaseThresholds
+     *      The decrease threshold values (throttled if {@code value > value * (1 - threshold)}).
+     *
+     * @throws IllegalArgumentException
+     *      - increaseDebounce is negative;
+     *      - decreaseDebounce is negative;
+     *      - baseThresholds to increaseThresholds is not a valid mapping*;
+     *      - baseThresholds to decreaseThresholds is not a valid mapping*;
+     *
+     *      (*) The x to y mapping is valid if:
+     *          - x and y are not null;
+     *          - x and y are not empty;
+     *          - x and y contain only non-negative numbers;
+     *          - x is strictly increasing.
+     */
+    DisplayWhiteBalanceThrottler(int increaseDebounce, int decreaseDebounce,
+            float[] baseThresholds, float[] increaseThresholds, float[] decreaseThresholds) {
+        validateArguments(increaseDebounce, decreaseDebounce, baseThresholds, increaseThresholds,
+                decreaseThresholds);
+        mLoggingEnabled = false;
+        mIncreaseDebounce = increaseDebounce;
+        mDecreaseDebounce = decreaseDebounce;
+        mBaseThresholds = baseThresholds;
+        mIncreaseThresholds = increaseThresholds;
+        mDecreaseThresholds = decreaseThresholds;
+        clear();
+    }
+
+    /**
+     * Check whether the ambient color temperature should be throttled.
+     *
+     * @param value
+     *      The ambient color temperature value.
+     *
+     * @return Whether the ambient color temperature should be throttled.
+     */
+    public boolean throttle(float value) {
+        if (mLastTime != -1 && (tooSoon(value) || tooClose(value))) {
+            return true;
+        }
+        computeThresholds(value);
+        mLastTime = System.currentTimeMillis();
+        mLastValue = value;
+        return false;
+    }
+
+    /**
+     * Clears the throttler state.
+     */
+    public void clear() {
+        mLastTime = -1;
+        mIncreaseThreshold = -1.0f;
+        mDecreaseThreshold = -1.0f;
+        mLastValue = -1.0f;
+    }
+
+    /**
+     * Enable/disable logging.
+     *
+     * @param loggingEnabled
+     *      Whether logging is on/off.
+     *
+     * @return Whether the method succeeded or not.
+     */
+    public boolean setLoggingEnabled(boolean loggingEnabled) {
+        if (mLoggingEnabled == loggingEnabled) {
+            return false;
+        }
+        mLoggingEnabled = loggingEnabled;
+        return true;
+    }
+
+    /**
+     * Dump the state.
+     *
+     * @param writer
+     *      The PrintWriter used to dump the state.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println("  DisplayWhiteBalanceThrottler");
+        writer.println("    mLoggingEnabled=" + mLoggingEnabled);
+        writer.println("    mIncreaseDebounce=" + mIncreaseDebounce);
+        writer.println("    mDecreaseDebounce=" + mDecreaseDebounce);
+        writer.println("    mLastTime=" + mLastTime);
+        writer.println("    mBaseThresholds=" + Arrays.toString(mBaseThresholds));
+        writer.println("    mIncreaseThresholds=" + Arrays.toString(mIncreaseThresholds));
+        writer.println("    mDecreaseThresholds=" + Arrays.toString(mDecreaseThresholds));
+        writer.println("    mIncreaseThreshold=" + mIncreaseThreshold);
+        writer.println("    mDecreaseThreshold=" + mDecreaseThreshold);
+        writer.println("    mLastValue=" + mLastValue);
+    }
+
+    private void validateArguments(float increaseDebounce, float decreaseDebounce,
+            float[] baseThresholds, float[] increaseThresholds, float[] decreaseThresholds) {
+        if (Float.isNaN(increaseDebounce) || increaseDebounce < 0.0f) {
+            throw new IllegalArgumentException("increaseDebounce must be a non-negative number.");
+        }
+        if (Float.isNaN(decreaseDebounce) || decreaseDebounce < 0.0f) {
+            throw new IllegalArgumentException("decreaseDebounce must be a non-negative number.");
+        }
+        if (!isValidMapping(baseThresholds, increaseThresholds)) {
+            throw new IllegalArgumentException(
+                    "baseThresholds to increaseThresholds is not a valid mapping.");
+        }
+        if (!isValidMapping(baseThresholds, decreaseThresholds)) {
+            throw new IllegalArgumentException(
+                    "baseThresholds to decreaseThresholds is not a valid mapping.");
+        }
+    }
+
+    private static boolean isValidMapping(float[] x, float[] y) {
+        if (x == null || y == null || x.length == 0 || y.length == 0 || x.length != y.length) {
+            return false;
+        }
+        float prevX = -1.0f;
+        for (int i = 0; i < x.length; i++) {
+            if (Float.isNaN(x[i]) || Float.isNaN(y[i]) || x[i] < 0 || prevX >= x[i]) {
+                return false;
+            }
+            prevX = x[i];
+        }
+        return true;
+    }
+
+    private boolean tooSoon(float value) {
+        final long time = System.currentTimeMillis();
+        final long earliestTime;
+        if (value > mLastValue) {
+            earliestTime = mLastTime + mIncreaseDebounce;
+        } else { // value <= mLastValue
+            earliestTime = mLastTime + mDecreaseDebounce;
+        }
+        final boolean tooSoon = time < earliestTime;
+        if (mLoggingEnabled) {
+            Slog.d(TAG, (tooSoon ? "too soon: " : "late enough: ") + time
+                    + (tooSoon ? " < " : " > ") + earliestTime);
+        }
+        return tooSoon;
+    }
+
+    private boolean tooClose(float value) {
+        final float threshold;
+        final boolean tooClose;
+        if (value > mLastValue) {
+            threshold = mIncreaseThreshold;
+            tooClose = value < threshold;
+        } else { // value <= mLastValue
+            threshold = mDecreaseThreshold;
+            tooClose = value > threshold;
+        }
+        if (mLoggingEnabled) {
+            Slog.d(TAG, (tooClose ? "too close: " : "far enough: ") + value
+                    + (value > threshold ? " > " : " < ") + threshold);
+        }
+        return tooClose;
+    }
+
+    private void computeThresholds(float value) {
+        final int index = getHighestIndexBefore(value, mBaseThresholds);
+        mIncreaseThreshold = value * (1.0f + mIncreaseThresholds[index]);
+        mDecreaseThreshold = value * (1.0f - mDecreaseThresholds[index]);
+    }
+
+    private int getHighestIndexBefore(float value, float[] values) {
+        for (int i = 0; i < values.length; i++) {
+            if (values[i] >= value) {
+                return i;
+            }
+        }
+        return values.length - 1;
+    }
+
+}
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 1cbf0bf..b0d2704 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -270,9 +270,7 @@
             Slog.d(TAG, "onTargetPackageUpgraded packageName=" + packageName + " userId=" + userId);
         }
 
-        if (updateAllOverlaysForTarget(packageName, userId, 0)) {
-            mListener.onOverlaysChanged(packageName, userId);
-        }
+        updateAllOverlaysForTarget(packageName, userId, 0);
     }
 
     void onTargetPackageRemoved(@NonNull final String packageName, final int userId) {
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 94b1b36..5b38208 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -507,7 +507,7 @@
         int flags = info.flags;
         boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
         // When an app or priv app is configured to run out of box, only verify it.
-        if (info.isCodeIntegrityPreferred()
+        if (info.isEmbeddedDexUsed()
                 || (info.isPrivilegedApp()
                     && DexManager.isPackageSelectedToRunOob(info.packageName))) {
             return "verify";
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index a3e0d8d..af50c87 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -204,13 +204,17 @@
         mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions");
         mSessionsDir.mkdirs();
 
-        mStagingManager = new StagingManager(pm);
+        mStagingManager = new StagingManager(pm, this);
     }
 
     private void setBootCompleted()  {
         mBootCompleted = true;
     }
 
+    boolean isBootCompleted()  {
+        return mBootCompleted;
+    }
+
     public void systemReady() {
         mAppOps = mContext.getSystemService(AppOpsManager.class);
 
@@ -245,13 +249,24 @@
             // the updated information.
             writeSessionsLocked();
 
+        }
+    }
+
+    void restoreAndApplyStagedSessionIfNeeded() {
+        List<PackageInstallerSession> stagedSessionsToRestore = new ArrayList<>();
+        synchronized (mSessions) {
             for (int i = 0; i < mSessions.size(); i++) {
                 final PackageInstallerSession session = mSessions.valueAt(i);
                 if (session.isStaged()) {
-                    mStagingManager.restoreSession(session);
+                    stagedSessionsToRestore.add(session);
                 }
             }
         }
+        // Don't hold mSessions lock when calling restoreSession, since it might trigger an APK
+        // atomic install which needs to query sessions, which requires lock on mSessions.
+        for (PackageInstallerSession session : stagedSessionsToRestore) {
+            mStagingManager.restoreSession(session);
+        }
     }
 
     @GuardedBy("mSessions")
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index de0849f..dc6751c4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1563,12 +1563,12 @@
                 }
             }
         }
-        if (baseApk.preferCodeIntegrity) {
+        if (baseApk.useEmbeddedDex) {
             for (File file : mResolvedStagedFiles) {
                 if (file.getName().endsWith(".apk")
-                        && !DexManager.auditUncompressedCodeInApk(file.getPath())) {
+                        && !DexManager.auditUncompressedDexInApk(file.getPath())) {
                     throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
-                            "Some code are not uncompressed and aligned correctly for "
+                            "Some dex are not uncompressed and aligned correctly for "
                             + mPackageName);
                 }
             }
@@ -1664,6 +1664,12 @@
         }
     }
 
+    String getInstallerPackageName() {
+        synchronized (mLock) {
+            return mInstallerPackageName;
+        }
+    }
+
     private static String getRelativePath(File file, File base) throws IOException {
         final String pathStr = file.getAbsolutePath();
         final String baseStr = base.getAbsolutePath();
@@ -1964,7 +1970,7 @@
 
         // Send broadcast to default launcher only if it's a new install
         final boolean isNewInstall = extras == null || !extras.getBoolean(Intent.EXTRA_REPLACING);
-        if (success && isNewInstall) {
+        if (success && isNewInstall && mPm.mInstallerService.isBootCompleted()) {
             mPm.sendSessionCommitBroadcast(generateInfo(), userId);
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 949a111..e370e6b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -20754,6 +20754,11 @@
         }
 
         mModuleInfoProvider.systemReady();
+
+        // Installer service might attempt to install some packages that have been staged for
+        // installation on reboot. Make sure this is the last component to be call since the
+        // installation might require other components to be ready.
+        mInstallerService.restoreAndApplyStagedSessionIfNeeded();
     }
 
     public void waitForAppDataPrepared() {
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index c4d27e5..1d1d2e6 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -21,6 +21,10 @@
 import android.apex.ApexInfoList;
 import android.apex.ApexSessionInfo;
 import android.apex.IApexService;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageManager;
@@ -29,7 +33,10 @@
 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.Signature;
+import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.text.TextUtils;
@@ -40,9 +47,13 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.BackgroundThread;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -53,14 +64,16 @@
 
     private static final String TAG = "StagingManager";
 
+    private final PackageInstallerService mPi;
     private final PackageManagerService mPm;
     private final Handler mBgHandler;
 
     @GuardedBy("mStagedSessions")
     private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
 
-    StagingManager(PackageManagerService pm) {
+    StagingManager(PackageManagerService pm, PackageInstallerService pi) {
         mPm = pm;
+        mPi = pi;
         mBgHandler = BackgroundThread.getHandler();
     }
 
@@ -85,7 +98,7 @@
         return new ParceledListSlice<>(result);
     }
 
-    private static boolean validateApexSignatureLocked(String apexPath, String packageName) {
+    private static boolean validateApexSignature(String apexPath, String packageName) {
         final SigningDetails signingDetails;
         try {
             signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
@@ -173,6 +186,13 @@
     private void preRebootVerification(@NonNull PackageInstallerSession session) {
         boolean success = true;
 
+        if (!sessionContainsApex(session)) {
+            // TODO: Decide whether we want to fail fast by detecting signature mismatches for APKs,
+            // right away.
+            session.setStagedSessionReady();
+            return;
+        }
+
         final ApexInfoList apexInfoList = new ApexInfoList();
         // APEX checks. For single-package sessions, check if they contain an APEX. For
         // multi-package sessions, find all the child sessions that contain an APEX.
@@ -199,16 +219,13 @@
                     "APEX staging failed, check logcat messages from apexd for more details.");
         }
 
-        if (apexInfoList.apexInfos.length > 0) {
+        if (apexInfoList.apexInfos != null && apexInfoList.apexInfos.length > 0) {
             // For APEXes, we validate the signature here before we mark the session as ready,
             // so we fail the session early if there is a signature mismatch. For APKs, the
             // signature verification will be done by the package manager at the point at which
             // it applies the staged install.
-            //
-            // TODO: Decide whether we want to fail fast by detecting signature mismatches for APKs,
-            // right away.
             for (ApexInfo apexPackage : apexInfoList.apexInfos) {
-                if (!validateApexSignatureLocked(apexPackage.packagePath,
+                if (!validateApexSignature(apexPackage.packagePath,
                         apexPackage.packageName)) {
                     session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED,
                             "APK-container signature verification failed for package "
@@ -229,35 +246,182 @@
         }
     }
 
+    private boolean sessionContainsApex(@NonNull PackageInstallerSession session) {
+        if (!session.isMultiPackage()) {
+            return isApexSession(session);
+        }
+        synchronized (mStagedSessions) {
+            return !(Arrays.stream(session.getChildSessionIds())
+                    // Retrieve cached sessions matching ids.
+                    .mapToObj(i -> mStagedSessions.get(i))
+                    // Filter only the ones containing APEX.
+                    .filter(childSession -> isApexSession(childSession))
+                    .collect(Collectors.toList())
+                    .isEmpty());
+        }
+    }
+
     private void resumeSession(@NonNull PackageInstallerSession session) {
-        // Check with apexservice whether the apex
-        // packages have been activated.
-        final IApexService apex = IApexService.Stub.asInterface(
-                ServiceManager.getService("apexservice"));
-        ApexSessionInfo apexSessionInfo;
-        try {
-            apexSessionInfo = apex.getStagedSessionInfo(session.sessionId);
-        } catch (RemoteException re) {
-            Slog.e(TAG, "Unable to contact apexservice", re);
-            // TODO should we retry here? Mark the session as failed?
+        if (sessionContainsApex(session)) {
+            // Check with apexservice whether the apex
+            // packages have been activated.
+            final IApexService apex = IApexService.Stub.asInterface(
+                    ServiceManager.getService("apexservice"));
+            ApexSessionInfo apexSessionInfo;
+            try {
+                apexSessionInfo = apex.getStagedSessionInfo(session.sessionId);
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Unable to contact apexservice", re);
+                // TODO should we retry here? Mark the session as failed?
+                return;
+            }
+            if (apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown) {
+                session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED,
+                        "APEX activation failed. Check logcat messages from apexd for "
+                                + "more information.");
+                return;
+            }
+            if (apexSessionInfo.isVerified) {
+                // Session has been previously submitted to apexd, but didn't complete all the
+                // pre-reboot verification, perhaps because the device rebooted in the meantime.
+                // Greedily re-trigger the pre-reboot verification.
+                Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to be "
+                        + "verified, resuming pre-reboot verification");
+                mBgHandler.post(() -> preRebootVerification(session));
+                return;
+            }
+            if (!apexSessionInfo.isActivated) {
+                // In all the remaining cases apexd will try to apply the session again at next
+                // boot. Nothing to do here for now.
+                Slog.w(TAG, "Staged session " + session.sessionId + " scheduled to be applied "
+                        + "at boot didn't activate nor fail. This usually means that apexd will "
+                        + "retry at next reboot.");
+                return;
+            }
+        }
+        // The APEX part of the session is activated, proceed with the installation of APKs.
+        if (!installApksInSession(session)) {
+            session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED,
+                    "APK installation for staged session " + session.sessionId + " failed.");
             return;
         }
-        if (apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown) {
-            session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED,
-                    "APEX activation failed. Check logcat messages from apexd for "
-                                  + "more information.");
+        session.setStagedSessionApplied();
+    }
+
+    private String findFirstAPKInDir(File stageDir) {
+        if (stageDir != null && stageDir.exists()) {
+            for (File file : stageDir.listFiles()) {
+                if (file.getAbsolutePath().toLowerCase().endsWith(".apk")) {
+                    return file.getAbsolutePath();
+                }
+            }
         }
-        if (apexSessionInfo.isVerified) {
-            // Session has been previously submitted to apexd, but didn't complete all the
-            // pre-reboot verification, perhaps because the device rebooted in the meantime.
-            // Greedily re-trigger the pre-reboot verification.
-            mBgHandler.post(() -> preRebootVerification(session));
+        return null;
+    }
+
+    private PackageInstallerSession createAndWriteApkSession(
+            @NonNull PackageInstallerSession originalSession) {
+        // TODO(b/123629153): support split APKs.
+        if (originalSession.stageDir == null) {
+            Slog.wtf(TAG, "Attempting to install a staged APK session with no staging dir");
+            return null;
         }
-        if (apexSessionInfo.isActivated) {
-            session.setStagedSessionApplied();
-            // TODO(b/118865310) if multi-package proceed with the installation of APKs.
+        String apkFilePath = findFirstAPKInDir(originalSession.stageDir);
+        if (apkFilePath == null) {
+            Slog.w(TAG, "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath());
+            return null;
         }
-        // In every other case apexd will retry to apply the session at next boot.
+        File apkFile = new File(apkFilePath);
+
+        PackageInstaller.SessionParams params = originalSession.params.copy();
+        params.isStaged = false;
+        int apkSessionId = mPi.createSession(
+                params, originalSession.getInstallerPackageName(), originalSession.userId);
+        PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
+
+        try {
+            apkSession.open();
+            ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
+                    ParcelFileDescriptor.MODE_READ_ONLY);
+            long sizeBytes = pfd.getStatSize();
+            if (sizeBytes < 0) {
+                Slog.e(TAG, "Unable to get size of: " + apkFilePath);
+                return null;
+            }
+            apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
+            return null;
+        }
+        return apkSession;
+    }
+
+    private boolean commitApkSession(@NonNull PackageInstallerSession apkSession,
+                                     int originalSessionId) {
+        final LocalIntentReceiver receiver = new LocalIntentReceiver();
+        apkSession.commit(receiver.getIntentSender(), false);
+        final Intent result = receiver.getResult();
+        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                PackageInstaller.STATUS_FAILURE);
+        if (status == PackageInstaller.STATUS_SUCCESS) {
+            return true;
+        }
+        Slog.e(TAG, "Failure to install APK staged session " + originalSessionId + " ["
+                + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+        return false;
+    }
+
+    private boolean installApksInSession(@NonNull PackageInstallerSession session) {
+        if (!session.isMultiPackage() && !isApexSession(session)) {
+            // APK single-packaged staged session. Do a regular install.
+            PackageInstallerSession apkSession = createAndWriteApkSession(session);
+            if (apkSession == null) {
+                return false;
+            }
+            return commitApkSession(apkSession, session.sessionId);
+        } else if (session.isMultiPackage()) {
+            // For multi-package staged sessions containing APKs, we identify which child sessions
+            // contain an APK, and with those then create a new multi-package group of sessions,
+            // carrying over all the session parameters and unmarking them as staged. On commit the
+            // sessions will be installed atomically.
+            List<PackageInstallerSession> childSessions;
+            synchronized (mStagedSessions) {
+                childSessions =
+                        Arrays.stream(session.getChildSessionIds())
+                                // Retrieve cached sessions matching ids.
+                                .mapToObj(i -> mStagedSessions.get(i))
+                                // Filter only the ones containing APKs.s
+                                .filter(childSession -> !isApexSession(childSession))
+                                .collect(Collectors.toList());
+            }
+            if (childSessions.isEmpty()) {
+                // APEX-only multi-package staged session, nothing to do.
+                return true;
+            }
+            PackageInstaller.SessionParams params = session.params.copy();
+            params.isStaged = false;
+            int apkParentSessionId = mPi.createSession(
+                    params, session.getInstallerPackageName(), session.userId);
+            PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
+            try {
+                apkParentSession.open();
+            } catch (IOException e) {
+                Slog.e(TAG, "Unable to prepare multi-package session for staged session "
+                        + session.sessionId);
+                return false;
+            }
+
+            for (PackageInstallerSession sessionToClone : childSessions) {
+                PackageInstallerSession apkChildSession = createAndWriteApkSession(sessionToClone);
+                if (apkChildSession == null) {
+                    return false;
+                }
+                apkParentSession.addChildSessionId(apkChildSession.sessionId);
+            }
+            return commitApkSession(apkParentSession, session.sessionId);
+        }
+        // APEX single-package staged session, nothing to do.
+        return true;
     }
 
     void commitSession(@NonNull PackageInstallerSession session) {
@@ -336,9 +500,39 @@
         } else {
             // Session had already being marked ready. Start the checks to verify if there is any
             // follow-up work.
-            // TODO(b/118865310): should this be synchronous to ensure it completes before
-            //                    systemReady() finishes?
+            // TODO(b/123690624): Resuming of session should happen before processes start. The
+            //  potential problem here is that installation of APKs in the staged session might
+            //  cause existing processes to restart.
             mBgHandler.post(() -> resumeSession(session));
         }
     }
+
+    private static class LocalIntentReceiver {
+        private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
+
+        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+            @Override
+            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+                             IIntentReceiver finishedReceiver, String requiredPermission,
+                             Bundle options) {
+                try {
+                    mResult.offer(intent, 5, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        };
+
+        public IntentSender getIntentSender() {
+            return new IntentSender((IIntentSender) mLocalSender);
+        }
+
+        public Intent getResult() {
+            try {
+                return mResult.take();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 7d2dd65..23705db 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -480,7 +480,7 @@
             final String apkPath = pkg.baseCodePath;
             final ApplicationInfo appInfo = pkg.applicationInfo;
             final String outDexFile = appInfo.dataDir + "/code_cache/compiled_view.dex";
-            if (appInfo.isPrivilegedApp() || appInfo.isCodeIntegrityPreferred()) {
+            if (appInfo.isPrivilegedApp() || appInfo.isEmbeddedDexUsed()) {
                 // Privileged apps prefer to load trusted code so they don't use compiled views.
                 // If the app is not privileged but prefers code integrity, also avoid compiling
                 // views.
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index abbddf3..2213901 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -740,10 +740,10 @@
     }
 
     /**
-     * Generates log if the archive located at {@code fileName} has uncompressed dex file and so
-     * files that can be direclty mapped.
+     * Generates log if the archive located at {@code fileName} has uncompressed dex file that can
+     * be direclty mapped.
      */
-    public static boolean auditUncompressedCodeInApk(String fileName) {
+    public static boolean auditUncompressedDexInApk(String fileName) {
         StrictJarFile jarFile = null;
         try {
             jarFile = new StrictJarFile(fileName,
@@ -762,16 +762,6 @@
                         Slog.w(TAG, "APK " + fileName + " has unaligned dex code " +
                                 entry.getName());
                     }
-                } else if (entry.getName().endsWith(".so")) {
-                    if (entry.getMethod() != ZipEntry.STORED) {
-                        allCorrect = false;
-                        Slog.w(TAG, "APK " + fileName + " has compressed native code " +
-                                entry.getName());
-                    } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
-                        allCorrect = false;
-                        Slog.w(TAG, "APK " + fileName + " has unaligned native code " +
-                                entry.getName());
-                    }
                 }
             }
             return allCorrect;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f176bc4..8f5ce74 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -14078,6 +14078,10 @@
 
         enforceCrossUsersPermission(userHandle);
         synchronized (getLockObject()) {
+            if (mInjector.settingsSecureGetIntForUser(
+                    Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED, 0, userHandle) == 0) {
+                return false;
+            }
             final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
             if (admin != null) {
                 if (admin.mCrossProfileCalendarPackages == null) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 535198b..9ac91dd 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5278,6 +5278,35 @@
         assertTrue(actual.containsAll(expected));
     }
 
+    public void testIsPackageAllowedToAccessCalendar_adminNotAllowed() {
+        setAsProfileOwner(admin1);
+        dpm.setCrossProfileCalendarPackages(admin1, Collections.emptySet());
+        when(getServices().settings.settingsSecureGetIntForUser(
+                Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED,
+                0, DpmMockContext.CALLER_USER_HANDLE)).thenReturn(1);
+        assertFalse(dpm.isPackageAllowedToAccessCalendar("TEST_PACKAGE"));
+    }
+
+    public void testIsPackageAllowedToAccessCalendar_settingOff() {
+        final String testPackage = "TEST_PACKAGE";
+        setAsProfileOwner(admin1);
+        dpm.setCrossProfileCalendarPackages(admin1, Collections.singleton(testPackage));
+        when(getServices().settings.settingsSecureGetIntForUser(
+                Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED,
+                0, DpmMockContext.CALLER_USER_HANDLE)).thenReturn(0);
+        assertFalse(dpm.isPackageAllowedToAccessCalendar(testPackage));
+    }
+
+    public void testIsPackageAllowedToAccessCalendar_bothAllowed() {
+        final String testPackage = "TEST_PACKAGE";
+        setAsProfileOwner(admin1);
+        dpm.setCrossProfileCalendarPackages(admin1, null);
+        when(getServices().settings.settingsSecureGetIntForUser(
+                Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED,
+                0, DpmMockContext.CALLER_USER_HANDLE)).thenReturn(1);
+        assertTrue(dpm.isPackageAllowedToAccessCalendar(testPackage));
+    }
+
     private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) {
         final long ident = mServiceContext.binder.clearCallingIdentity();
         mServiceContext.binder.callingUid =
diff --git a/startop/iorap/TEST_MAPPING b/startop/iorap/DISABLED_TEST_MAPPING
similarity index 100%
rename from startop/iorap/TEST_MAPPING
rename to startop/iorap/DISABLED_TEST_MAPPING
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index f330b83..1a4ec94 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -1027,5 +1027,15 @@
             </intent-filter>
         </activity>
 
+        <activity
+            android:name="PositionListenerActivity"
+            android:label="RenderNode/PositionListener"
+            android:screenOrientation="fullSensor">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java
new file mode 100644
index 0000000..316aad3
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2010 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.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.RenderNode;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class PositionListenerActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final LinearLayout layout = new LinearLayout(this);
+        layout.setOrientation(LinearLayout.VERTICAL);
+
+        ProgressBar spinner = new ProgressBar(this, null, android.R.attr.progressBarStyleLarge);
+        layout.addView(spinner);
+
+        ScrollView scrollingThing = new ScrollView(this);
+        scrollingThing.addView(new MyPositionReporter(this));
+        layout.addView(scrollingThing);
+
+        setContentView(layout);
+    }
+
+    static class MyPositionReporter extends TextView implements RenderNode.PositionUpdateListener {
+        RenderNode mNode;
+        int mCurrentCount = 0;
+        int mTranslateY = 0;
+
+        MyPositionReporter(Context c) {
+            super(c);
+            mNode = new RenderNode("positionListener");
+            mNode.requestPositionUpdates(this);
+            setTextAlignment(TEXT_ALIGNMENT_VIEW_START);
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            setMeasuredDimension(getMeasuredWidth(), 10000);
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            mNode.setLeftTopRightBottom(left, top, right, bottom);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            ScrollView parent = (ScrollView) getParent();
+            canvas.translate(0, parent.getScrollY());
+            super.onDraw(canvas);
+            canvas.translate(0, -parent.getScrollY());
+            // Inject our listener proxy
+            canvas.drawRenderNode(mNode);
+        }
+
+        @Override
+        public void positionChanged(long frameNumber, int left, int top, int right, int bottom) {
+            post(() -> {
+                mCurrentCount++;
+                setText(String.format("%d: Position [%d, %d, %d, %d]", mCurrentCount,
+                        left, top, right, bottom));
+            });
+        }
+
+        @Override
+        public void positionLost(long frameNumber) {
+            post(() -> {
+                mCurrentCount++;
+                setText(mCurrentCount + " No position");
+            });
+        }
+    }
+}