Merge "Fix idmap2 compiling against musl" into stage-aosp-master am: 5f9fe6e37c am: 866e277bc6

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15839888

Change-Id: I23fe2b8851404c74fbe140f226f668ee06c862ee
diff --git a/cmds/bootanimation/Android.bp b/cmds/bootanimation/Android.bp
index b2b66c2..3534624 100644
--- a/cmds/bootanimation/Android.bp
+++ b/cmds/bootanimation/Android.bp
@@ -71,7 +71,7 @@
         "libui",
         "libjnigraphics",
         "libEGL",
-        "libGLESv1_CM",
+        "libGLESv2",
         "libgui",
     ],
 }
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 3109c5c..6c8cab7 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -52,9 +52,8 @@
 #include <gui/DisplayEventReceiver.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
 #include <EGL/eglext.h>
 
 #include "BootAnimation.h"
@@ -108,6 +107,93 @@
 static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
 static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
 static constexpr size_t TEXT_POS_LEN_MAX = 16;
+static const int DYNAMIC_COLOR_COUNT = 4;
+static const char U_TEXTURE[] = "uTexture";
+static const char U_FADE[] = "uFade";
+static const char U_CROP_AREA[] = "uCropArea";
+static const char U_START_COLOR_PREFIX[] = "uStartColor";
+static const char U_END_COLOR_PREFIX[] = "uEndColor";
+static const char U_COLOR_PROGRESS[] = "uColorProgress";
+static const char A_UV[] = "aUv";
+static const char A_POSITION[] = "aPosition";
+static const char VERTEX_SHADER_SOURCE[] = R"(
+    precision mediump float;
+    attribute vec4 aPosition;
+    attribute highp vec2 aUv;
+    varying highp vec2 vUv;
+    void main() {
+        gl_Position = aPosition;
+        vUv = aUv;
+    })";
+static const char IMAGE_FRAG_DYNAMIC_COLORING_SHADER_SOURCE[] = R"(
+    precision mediump float;
+    const float cWhiteMaskThreshold = 0.05;
+    uniform sampler2D uTexture;
+    uniform float uFade;
+    uniform float uColorProgress;
+    uniform vec4 uStartColor0;
+    uniform vec4 uStartColor1;
+    uniform vec4 uStartColor2;
+    uniform vec4 uStartColor3;
+    uniform vec4 uEndColor0;
+    uniform vec4 uEndColor1;
+    uniform vec4 uEndColor2;
+    uniform vec4 uEndColor3;
+    varying highp vec2 vUv;
+    void main() {
+        vec4 mask = texture2D(uTexture, vUv);
+        float r = mask.r;
+        float g = mask.g;
+        float b = mask.b;
+        float a = mask.a;
+        // If all channels have values, render pixel as a shade of white.
+        float useWhiteMask = step(cWhiteMaskThreshold, r)
+            * step(cWhiteMaskThreshold, g)
+            * step(cWhiteMaskThreshold, b)
+            * step(cWhiteMaskThreshold, a);
+        vec4 color = r * mix(uStartColor0, uEndColor0, uColorProgress)
+                + g * mix(uStartColor1, uEndColor1, uColorProgress)
+                + b * mix(uStartColor2, uEndColor2, uColorProgress)
+                + a * mix(uStartColor3, uEndColor3, uColorProgress);
+        color = mix(color, vec4(vec3((r + g + b + a) * 0.25), 1.0), useWhiteMask);
+        gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade)) * color.a;
+    })";
+static const char IMAGE_FRAG_SHADER_SOURCE[] = R"(
+    precision mediump float;
+    uniform sampler2D uTexture;
+    uniform float uFade;
+    varying highp vec2 vUv;
+    void main() {
+        vec4 color = texture2D(uTexture, vUv);
+        gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade)) * color.a;
+    })";
+static const char TEXT_FRAG_SHADER_SOURCE[] = R"(
+    precision mediump float;
+    uniform sampler2D uTexture;
+    uniform vec4 uCropArea;
+    varying highp vec2 vUv;
+    void main() {
+        vec2 uv = vec2(mix(uCropArea.x, uCropArea.z, vUv.x),
+                       mix(uCropArea.y, uCropArea.w, vUv.y));
+        gl_FragColor = texture2D(uTexture, uv);
+    })";
+
+static GLfloat quadPositions[] = {
+    -0.5f, -0.5f,
+    +0.5f, -0.5f,
+    +0.5f, +0.5f,
+    +0.5f, +0.5f,
+    -0.5f, +0.5f,
+    -0.5f, -0.5f
+};
+static GLfloat quadUVs[] = {
+    0.0f, 1.0f,
+    1.0f, 1.0f,
+    1.0f, 0.0f,
+    1.0f, 0.0f,
+    0.0f, 0.0f,
+    0.0f, 1.0f
+};
 
 // ---------------------------------------------------------------------------
 
@@ -163,7 +249,8 @@
     requestExit();
 }
 
-static void* decodeImage(const void* encodedData, size_t dataLength, AndroidBitmapInfo* outInfo) {
+static void* decodeImage(const void* encodedData, size_t dataLength, AndroidBitmapInfo* outInfo,
+    bool premultiplyAlpha) {
     AImageDecoder* decoder = nullptr;
     AImageDecoder_createFromBuffer(encodedData, dataLength, &decoder);
     if (!decoder) {
@@ -177,6 +264,10 @@
     outInfo->stride = AImageDecoder_getMinimumStride(decoder);
     outInfo->flags = 0;
 
+    if (!premultiplyAlpha) {
+        AImageDecoder_setUnpremultipliedRequired(decoder, true);
+    }
+
     const size_t size = outInfo->stride * outInfo->height;
     void* pixels = malloc(size);
     int result = AImageDecoder_decodeImage(decoder, pixels, outInfo->stride, size);
@@ -190,13 +281,14 @@
 }
 
 status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
-        const char* name) {
+        const char* name, bool premultiplyAlpha) {
     Asset* asset = assets.open(name, Asset::ACCESS_BUFFER);
     if (asset == nullptr)
         return NO_INIT;
 
     AndroidBitmapInfo bitmapInfo;
-    void* pixels = decodeImage(asset->getBuffer(false), asset->getLength(), &bitmapInfo);
+    void* pixels = decodeImage(asset->getBuffer(false), asset->getLength(), &bitmapInfo,
+        premultiplyAlpha);
     auto pixelDeleter = std::unique_ptr<void, decltype(free)*>{ pixels, free };
 
     asset->close();
@@ -209,7 +301,6 @@
     const int w = bitmapInfo.width;
     const int h = bitmapInfo.height;
 
-    GLint crop[4] = { 0, h, w, -h };
     texture->w = w;
     texture->h = h;
 
@@ -237,18 +328,19 @@
             break;
     }
 
-    glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
-    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
-    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
     return NO_ERROR;
 }
 
-status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) {
+status_t BootAnimation::initTexture(FileMap* map, int* width, int* height,
+    bool premultiplyAlpha) {
     AndroidBitmapInfo bitmapInfo;
-    void* pixels = decodeImage(map->getDataPtr(), map->getDataLength(), &bitmapInfo);
+    void* pixels = decodeImage(map->getDataPtr(), map->getDataLength(), &bitmapInfo,
+        premultiplyAlpha);
     auto pixelDeleter = std::unique_ptr<void, decltype(free)*>{ pixels, free };
 
     // FileMap memory is never released until application exit.
@@ -263,7 +355,6 @@
     const int w = bitmapInfo.width;
     const int h = bitmapInfo.height;
 
-    GLint crop[4] = { 0, h, w, -h };
     int tw = 1 << (31 - __builtin_clz(w));
     int th = 1 << (31 - __builtin_clz(h));
     if (tw < w) tw <<= 1;
@@ -297,7 +388,10 @@
             break;
     }
 
-    glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
     *width = w;
     *height = h;
@@ -470,7 +564,9 @@
     eglInitialize(display, nullptr, nullptr);
     EGLConfig config = getEglConfig(display);
     EGLSurface surface = eglCreateWindowSurface(display, config, s.get(), nullptr);
-    EGLContext context = eglCreateContext(display, config, nullptr, nullptr);
+    // Initialize egl context with client version number 2.0.
+    EGLint contextAttributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
+    EGLContext context = eglCreateContext(display, config, nullptr, contextAttributes);
     EGLint w, h;
     eglQuerySurface(display, surface, EGL_WIDTH, &w);
     eglQuerySurface(display, surface, EGL_HEIGHT, &h);
@@ -503,11 +599,6 @@
 void BootAnimation::projectSceneToWindow() {
     glViewport(0, 0, mWidth, mHeight);
     glScissor(0, 0, mWidth, mHeight);
-    glMatrixMode(GL_PROJECTION);
-    glLoadIdentity();
-    glOrthof(0, static_cast<float>(mWidth), 0, static_cast<float>(mHeight), -1, 1);
-    glMatrixMode(GL_MODELVIEW);
-    glLoadIdentity();
 }
 
 void BootAnimation::resizeSurface(int newWidth, int newHeight) {
@@ -600,8 +691,76 @@
     }
 }
 
+GLuint compileShader(GLenum shaderType, const GLchar *source) {
+    GLuint shader = glCreateShader(shaderType);
+    glShaderSource(shader, 1, &source, 0);
+    glCompileShader(shader);
+    GLint isCompiled = 0;
+    glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
+    if (isCompiled == GL_FALSE) {
+        SLOGE("Compile shader failed. Shader type: %d", shaderType);
+        GLint maxLength = 0;
+        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
+        std::vector<GLchar> errorLog(maxLength);
+        glGetShaderInfoLog(shader, maxLength, &maxLength, &errorLog[0]);
+        SLOGE("Shader compilation error: %s", &errorLog[0]);
+        return 0;
+    }
+    return shader;
+}
+
+GLuint linkShader(GLuint vertexShader, GLuint fragmentShader) {
+    GLuint program = glCreateProgram();
+    glAttachShader(program, vertexShader);
+    glAttachShader(program, fragmentShader);
+    glLinkProgram(program);
+    GLint isLinked = 0;
+    glGetProgramiv(program, GL_LINK_STATUS, (int *)&isLinked);
+    if (isLinked == GL_FALSE) {
+        SLOGE("Linking shader failed. Shader handles: vert %d, frag %d",
+            vertexShader, fragmentShader);
+        return 0;
+    }
+    return program;
+}
+
+void BootAnimation::initShaders() {
+    bool dynamicColoringEnabled = mAnimation != nullptr && mAnimation->dynamicColoringEnabled;
+    GLuint vertexShader = compileShader(GL_VERTEX_SHADER, (const GLchar *)VERTEX_SHADER_SOURCE);
+    GLuint imageFragmentShader =
+        compileShader(GL_FRAGMENT_SHADER, dynamicColoringEnabled
+            ? (const GLchar *)IMAGE_FRAG_DYNAMIC_COLORING_SHADER_SOURCE
+            : (const GLchar *)IMAGE_FRAG_SHADER_SOURCE);
+    GLuint textFragmentShader =
+        compileShader(GL_FRAGMENT_SHADER, (const GLchar *)TEXT_FRAG_SHADER_SOURCE);
+
+    // Initialize image shader.
+    mImageShader = linkShader(vertexShader, imageFragmentShader);
+    GLint positionLocation = glGetAttribLocation(mImageShader, A_POSITION);
+    GLint uvLocation = glGetAttribLocation(mImageShader, A_UV);
+    mImageTextureLocation = glGetUniformLocation(mImageShader, U_TEXTURE);
+    mImageFadeLocation = glGetUniformLocation(mImageShader, U_FADE);
+    glEnableVertexAttribArray(positionLocation);
+    glVertexAttribPointer(positionLocation, 2,  GL_FLOAT, GL_FALSE, 0, quadPositions);
+    glVertexAttribPointer(uvLocation, 2, GL_FLOAT, GL_FALSE, 0, quadUVs);
+    glEnableVertexAttribArray(uvLocation);
+
+    // Initialize text shader.
+    mTextShader = linkShader(vertexShader, textFragmentShader);
+    positionLocation = glGetAttribLocation(mTextShader, A_POSITION);
+    uvLocation = glGetAttribLocation(mTextShader, A_UV);
+    mTextTextureLocation = glGetUniformLocation(mTextShader, U_TEXTURE);
+    mTextCropAreaLocation = glGetUniformLocation(mTextShader, U_CROP_AREA);
+    glEnableVertexAttribArray(positionLocation);
+    glVertexAttribPointer(positionLocation, 2,  GL_FLOAT, GL_FALSE, 0, quadPositions);
+    glVertexAttribPointer(uvLocation, 2, GL_FLOAT, GL_FALSE, 0, quadUVs);
+    glEnableVertexAttribArray(uvLocation);
+}
+
 bool BootAnimation::threadLoop() {
     bool result;
+    initShaders();
+
     // We have no bootanimation file, so we use the stock android logo
     // animation.
     if (mZipFileName.isEmpty()) {
@@ -623,6 +782,8 @@
 }
 
 bool BootAnimation::android() {
+    glActiveTexture(GL_TEXTURE0);
+
     SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
             elapsedRealtime());
     initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
@@ -631,19 +792,16 @@
     mCallbacks->init({});
 
     // clear screen
-    glShadeModel(GL_FLAT);
     glDisable(GL_DITHER);
     glDisable(GL_SCISSOR_TEST);
+    glUseProgram(mImageShader);
+
     glClearColor(0,0,0,1);
     glClear(GL_COLOR_BUFFER_BIT);
     eglSwapBuffers(mDisplay, mSurface);
 
-    glEnable(GL_TEXTURE_2D);
-    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
-
     // Blend state
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
 
     const nsecs_t startTime = systemTime();
     do {
@@ -666,12 +824,12 @@
         glEnable(GL_SCISSOR_TEST);
         glDisable(GL_BLEND);
         glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
-        glDrawTexiOES(x,                 yc, 0, mAndroid[1].w, mAndroid[1].h);
-        glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);
+        drawTexturedQuad(x,                 yc, mAndroid[1].w, mAndroid[1].h);
+        drawTexturedQuad(x + mAndroid[1].w, yc, mAndroid[1].w, mAndroid[1].h);
 
         glEnable(GL_BLEND);
         glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
-        glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);
+        drawTexturedQuad(xc, yc, mAndroid[0].w, mAndroid[0].h);
 
         EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
         if (res == EGL_FALSE)
@@ -766,6 +924,20 @@
     return true;
 }
 
+// Parse a color represented as a signed decimal int string.
+// E.g. "-2757722" (whose hex 2's complement is 0xFFD5EBA6).
+// If the input color string is empty, set color with values in defaultColor.
+static void parseColorDecimalString(const std::string& colorString,
+    float color[3], float defaultColor[3]) {
+    if (colorString == "") {
+        memcpy(color, defaultColor, sizeof(float) * 3);
+        return;
+    }
+    int colorInt = atoi(colorString.c_str());
+    color[0] = ((float)((colorInt >> 16) & 0xFF)) / 0xFF; // r
+    color[1] = ((float)((colorInt >> 8) & 0xFF)) / 0xFF; // g
+    color[2] = ((float)(colorInt & 0xFF)) / 0xFF; // b
+}
 
 static bool readFile(ZipFileRO* zip, const char* name, String8& outString) {
     ZipEntryRO entry = zip->findEntryByName(name);
@@ -798,10 +970,10 @@
 
         status = initTexture(font->map, &font->texture.w, &font->texture.h);
 
-        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
-        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
     } else if (fallback != nullptr) {
         status = initTexture(&font->texture, mAssets, fallback);
     } else {
@@ -816,40 +988,11 @@
     return status;
 }
 
-void BootAnimation::fadeFrame(const int frameLeft, const int frameBottom, const int frameWidth,
-                              const int frameHeight, const Animation::Part& part,
-                              const int fadedFramesCount) {
-    glEnable(GL_BLEND);
-    glEnableClientState(GL_VERTEX_ARRAY);
-    glDisable(GL_TEXTURE_2D);
-    // avoid creating a hole due to mixing result alpha with GL_REPLACE texture
-    glBlendFuncSeparateOES(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
-
-    const float alpha = static_cast<float>(fadedFramesCount) / part.framesToFadeCount;
-    glColor4f(part.backgroundColor[0], part.backgroundColor[1], part.backgroundColor[2], alpha);
-
-    const float frameStartX = static_cast<float>(frameLeft);
-    const float frameStartY = static_cast<float>(frameBottom);
-    const float frameEndX = frameStartX + frameWidth;
-    const float frameEndY = frameStartY + frameHeight;
-    const GLfloat frameRect[] = {
-        frameStartX, frameStartY,
-        frameEndX,   frameStartY,
-        frameEndX,   frameEndY,
-        frameStartX, frameEndY
-    };
-    glVertexPointer(2, GL_FLOAT, 0, frameRect);
-    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
-
-    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-    glEnable(GL_TEXTURE_2D);
-    glDisableClientState(GL_VERTEX_ARRAY);
-    glDisable(GL_BLEND);
-}
-
 void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) {
     glEnable(GL_BLEND);  // Allow us to draw on top of the animation
     glBindTexture(GL_TEXTURE_2D, font.texture.name);
+    glUseProgram(mTextShader);
+    glUniform1i(mTextTextureLocation, 0);
 
     const int len = strlen(str);
     const int strWidth = font.char_width * len;
@@ -865,8 +1008,6 @@
         *y = mHeight + *y - font.char_height;
     }
 
-    int cropRect[4] = { 0, 0, font.char_width, -font.char_height };
-
     for (int i = 0; i < len; i++) {
         char c = str[i];
 
@@ -878,13 +1019,13 @@
         const int charPos = (c - FONT_BEGIN_CHAR);  // Position in the list of valid characters
         const int row = charPos / FONT_NUM_COLS;
         const int col = charPos % FONT_NUM_COLS;
-        cropRect[0] = col * font.char_width;  // Left of column
-        cropRect[1] = row * font.char_height * 2; // Top of row
-        // Move down to bottom of regular (one char_heigh) or bold (two char_heigh) line
-        cropRect[1] += bold ? 2 * font.char_height : font.char_height;
-        glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect);
-
-        glDrawTexiOES(*x, *y, 0, font.char_width, font.char_height);
+        // Bold fonts are expected in the second half of each row.
+        float v0 = (row + (bold ? 0.5f : 0.0f)) / FONT_NUM_ROWS;
+        float u0 = ((float)col) / FONT_NUM_COLS;
+        float v1 = v0 + 1.0f / FONT_NUM_ROWS / 2;
+        float u1 = u0 + 1.0f / FONT_NUM_COLS;
+        glUniform4f(mTextCropAreaLocation, u0, v0, u1, v1);
+        drawTexturedQuad(*x, *y, font.char_width, font.char_height);
 
         *x += font.char_width;
     }
@@ -938,6 +1079,8 @@
         return false;
     }
     char const* s = desString.string();
+    std::string dynamicColoringPartName = "";
+    bool postDynamicColoring = false;
 
     // Parse the description file
     for (;;) {
@@ -952,11 +1095,19 @@
         int pause = 0;
         int progress = 0;
         int framesToFadeCount = 0;
+        int colorTransitionStart = 0;
+        int colorTransitionEnd = 0;
         char path[ANIM_ENTRY_NAME_MAX];
         char color[7] = "000000"; // default to black if unspecified
         char clockPos1[TEXT_POS_LEN_MAX + 1] = "";
         char clockPos2[TEXT_POS_LEN_MAX + 1] = "";
+        char dynamicColoringPartNameBuffer[ANIM_ENTRY_NAME_MAX];
         char pathType;
+        // start colors default to black if unspecified
+        char start_color_0[7] = "000000";
+        char start_color_1[7] = "000000";
+        char start_color_2[7] = "000000";
+        char start_color_3[7] = "000000";
 
         int nextReadPos;
 
@@ -971,6 +1122,18 @@
             } else {
               animation.progressEnabled = false;
             }
+        } else if (sscanf(l, "dynamic_colors %" STRTO(ANIM_PATH_MAX) "s #%6s #%6s #%6s #%6s %d %d",
+            dynamicColoringPartNameBuffer,
+            start_color_0, start_color_1, start_color_2, start_color_3,
+            &colorTransitionStart, &colorTransitionEnd)) {
+            animation.dynamicColoringEnabled = true;
+            parseColor(start_color_0, animation.startColors[0]);
+            parseColor(start_color_1, animation.startColors[1]);
+            parseColor(start_color_2, animation.startColors[2]);
+            parseColor(start_color_3, animation.startColors[3]);
+            animation.colorTransitionStart = colorTransitionStart;
+            animation.colorTransitionEnd = colorTransitionEnd;
+            dynamicColoringPartName = std::string(dynamicColoringPartNameBuffer);
         } else if (sscanf(l, "%c %d %d %" STRTO(ANIM_PATH_MAX) "s%n",
                           &pathType, &count, &pause, path, &nextReadPos) >= 4) {
             if (pathType == 'f') {
@@ -983,6 +1146,16 @@
             //       "clockPos1=%s, clockPos2=%s",
             //       pathType, count, pause, path, framesToFadeCount, color, clockPos1, clockPos2);
             Animation::Part part;
+            if (path == dynamicColoringPartName) {
+                // Part is specified to use dynamic coloring.
+                part.useDynamicColoring = true;
+                part.postDynamicColoring = false;
+                postDynamicColoring = true;
+            } else {
+                // Part does not use dynamic coloring.
+                part.useDynamicColoring = false;
+                part.postDynamicColoring =  postDynamicColoring;
+            }
             part.playUntilComplete = pathType == 'c';
             part.framesToFadeCount = framesToFadeCount;
             part.count = count;
@@ -1166,19 +1339,16 @@
 
     // Blend required to draw time on top of animation frames.
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-    glShadeModel(GL_FLAT);
     glDisable(GL_DITHER);
     glDisable(GL_SCISSOR_TEST);
     glDisable(GL_BLEND);
 
-    glBindTexture(GL_TEXTURE_2D, 0);
     glEnable(GL_TEXTURE_2D);
-    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
-    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
-    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
-    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
+    glBindTexture(GL_TEXTURE_2D, 0);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
     bool clockFontInitialized = false;
     if (mClockEnabled) {
         clockFontInitialized =
@@ -1193,6 +1363,10 @@
         mTimeCheckThread->run("BootAnimation::TimeCheckThread", PRIORITY_NORMAL);
     }
 
+    if (mAnimation != nullptr && mAnimation->dynamicColoringEnabled) {
+        initDynamicColors();
+    }
+
     playAnimation(*mAnimation);
 
     if (mTimeCheckThread != nullptr) {
@@ -1218,6 +1392,55 @@
         (lastDisplayedProgress == 0 || lastDisplayedProgress == 100);
 }
 
+// Linear mapping from range <a1, a2> to range <b1, b2>
+float mapLinear(float x, float a1, float a2, float b1, float b2) {
+    return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
+}
+
+void BootAnimation::drawTexturedQuad(float xStart, float yStart, float width, float height) {
+    // Map coordinates from screen space to world space.
+    float x0 = mapLinear(xStart, 0, mWidth, -1, 1);
+    float y0 = mapLinear(yStart, 0, mHeight, -1, 1);
+    float x1 = mapLinear(xStart + width, 0, mWidth, -1, 1);
+    float y1 = mapLinear(yStart + height, 0, mHeight, -1, 1);
+    // Update quad vertex positions.
+    quadPositions[0] = x0;
+    quadPositions[1] = y0;
+    quadPositions[2] = x1;
+    quadPositions[3] = y0;
+    quadPositions[4] = x1;
+    quadPositions[5] = y1;
+    quadPositions[6] = x1;
+    quadPositions[7] = y1;
+    quadPositions[8] = x0;
+    quadPositions[9] = y1;
+    quadPositions[10] = x0;
+    quadPositions[11] = y0;
+    glDrawArrays(GL_TRIANGLES, 0,
+        sizeof(quadPositions) / sizeof(quadPositions[0]) / 2);
+}
+
+void BootAnimation::initDynamicColors() {
+    for (int i = 0; i < DYNAMIC_COLOR_COUNT; i++) {
+        parseColorDecimalString(
+            android::base::GetProperty("persist.bootanim.color" + std::to_string(i + 1), ""),
+            mAnimation->endColors[i], mAnimation->startColors[i]);
+    }
+    glUseProgram(mImageShader);
+    SLOGI("[BootAnimation] Dynamically coloring boot animation.");
+    for (int i = 0; i < DYNAMIC_COLOR_COUNT; i++) {
+        float *startColor = mAnimation->startColors[i];
+        float *endColor = mAnimation->endColors[i];
+        glUniform4f(glGetUniformLocation(mImageShader,
+            (U_START_COLOR_PREFIX + std::to_string(i)).c_str()),
+            startColor[0], startColor[1], startColor[2], 1 /* alpha */);
+        glUniform4f(glGetUniformLocation(mImageShader,
+            (U_END_COLOR_PREFIX + std::to_string(i)).c_str()),
+            endColor[0], endColor[1], endColor[2], 1 /* alpha */);
+    }
+    mImageColorProgressLocation = glGetUniformLocation(mImageShader, U_COLOR_PROGRESS);
+}
+
 bool BootAnimation::playAnimation(const Animation& animation) {
     const size_t pcount = animation.parts.size();
     nsecs_t frameDuration = s2ns(1) / animation.fps;
@@ -1230,7 +1453,6 @@
     for (size_t i=0 ; i<pcount ; i++) {
         const Animation::Part& part(animation.parts[i]);
         const size_t fcount = part.frames.size();
-        glBindTexture(GL_TEXTURE_2D, 0);
 
         // Handle animation package
         if (part.animation != nullptr) {
@@ -1261,6 +1483,19 @@
             for (size_t j=0 ; j<fcount ; j++) {
                 if (shouldStopPlayingPart(part, fadedFramesCount, lastDisplayedProgress)) break;
 
+                // Color progress is
+                // - the animation progress, normalized from
+                //   [colorTransitionStart,colorTransitionEnd] to [0, 1] for the dynamic coloring
+                //   part.
+                // - 0 for parts that come before,
+                // - 1 for parts that come after.
+                float colorProgress = part.useDynamicColoring
+                    ? fmin(fmax(
+                        ((float)j - animation.colorTransitionStart) /
+                            fmax(animation.colorTransitionEnd -
+                                animation.colorTransitionStart, 1.0f), 0.0f), 1.0f)
+                    : (part.postDynamicColoring ? 1 : 0);
+
                 processDisplayEvents();
 
                 const int animationX = (mWidth - animation.width) / 2;
@@ -1272,44 +1507,38 @@
                 if (r > 0) {
                     glBindTexture(GL_TEXTURE_2D, frame.tid);
                 } else {
-                    if (part.count != 1) {
-                        glGenTextures(1, &frame.tid);
-                        glBindTexture(GL_TEXTURE_2D, frame.tid);
-                        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-                        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-                    }
+                    glGenTextures(1, &frame.tid);
+                    glBindTexture(GL_TEXTURE_2D, frame.tid);
                     int w, h;
-                    initTexture(frame.map, &w, &h);
+                    // Set decoding option to alpha unpremultiplied so that the R, G, B channels
+                    // of transparent pixels are preserved.
+                    initTexture(frame.map, &w, &h, false /* don't premultiply alpha */);
                 }
 
                 const int xc = animationX + frame.trimX;
                 const int yc = animationY + frame.trimY;
-                Region clearReg(Rect(mWidth, mHeight));
-                clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight));
-                if (!clearReg.isEmpty()) {
-                    Region::const_iterator head(clearReg.begin());
-                    Region::const_iterator tail(clearReg.end());
-                    glEnable(GL_SCISSOR_TEST);
-                    while (head != tail) {
-                        const Rect& r2(*head++);
-                        glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height());
-                        glClear(GL_COLOR_BUFFER_BIT);
-                    }
-                    glDisable(GL_SCISSOR_TEST);
-                }
+                glClear(GL_COLOR_BUFFER_BIT);
                 // specify the y center as ceiling((mHeight - frame.trimHeight) / 2)
                 // which is equivalent to mHeight - (yc + frame.trimHeight)
                 const int frameDrawY = mHeight - (yc + frame.trimHeight);
-                glDrawTexiOES(xc, frameDrawY, 0, frame.trimWidth, frame.trimHeight);
 
+                float fade = 0;
                 // if the part hasn't been stopped yet then continue fading if necessary
                 if (exitPending() && part.hasFadingPhase()) {
-                    fadeFrame(xc, frameDrawY, frame.trimWidth, frame.trimHeight, part,
-                              ++fadedFramesCount);
+                    fade = static_cast<float>(++fadedFramesCount) / part.framesToFadeCount;
                     if (fadedFramesCount >= part.framesToFadeCount) {
                         fadedFramesCount = MAX_FADED_FRAMES_COUNT; // no more fading
                     }
                 }
+                glUseProgram(mImageShader);
+                glUniform1i(mImageTextureLocation, 0);
+                glUniform1f(mImageFadeLocation, fade);
+                if (animation.dynamicColoringEnabled) {
+                    glUniform1f(mImageColorProgressLocation, colorProgress);
+                }
+                glEnable(GL_BLEND);
+                drawTexturedQuad(xc, frameDrawY, frame.trimWidth, frame.trimHeight);
+                glDisable(GL_BLEND);
 
                 if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
                     drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index f8a31c6..7a597da 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -31,7 +31,7 @@
 #include <binder/IBinder.h>
 
 #include <EGL/egl.h>
-#include <GLES/gl.h>
+#include <GLES2/gl2.h>
 
 namespace android {
 
@@ -53,7 +53,7 @@
     };
 
     struct Font {
-        FileMap* map;
+        FileMap* map = nullptr;
         Texture texture;
         int char_width;
         int char_height;
@@ -62,7 +62,7 @@
     struct Animation {
         struct Frame {
             String8 name;
-            FileMap* map;
+            FileMap* map = nullptr;
             int trimX;
             int trimY;
             int trimWidth;
@@ -90,6 +90,10 @@
             uint8_t* audioData;
             int audioLength;
             Animation* animation;
+            // Controls if dynamic coloring is enabled for this part.
+            bool useDynamicColoring = false;
+            // Defines if this part is played after the dynamic coloring part.
+            bool postDynamicColoring = false;
 
             bool hasFadingPhase() const {
                 return !playUntilComplete && framesToFadeCount > 0;
@@ -105,6 +109,12 @@
         ZipFileRO* zip;
         Font clockFont;
         Font progressFont;
+         // Controls if dynamic coloring is enabled for the whole animation.
+        bool dynamicColoringEnabled = false;
+        int colorTransitionStart = 0; // Start frame of dynamic color transition.
+        int colorTransitionEnd = 0; // End frame of dynamic color transition.
+        float startColors[4][3]; // Start colors of dynamic color transition.
+        float endColors[4][3];   // End colors of dynamic color transition.
     };
 
     // All callbacks will be called from this class's internal thread.
@@ -163,9 +173,12 @@
     int displayEventCallback(int fd, int events, void* data);
     void processDisplayEvents();
 
-    status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
-    status_t initTexture(FileMap* map, int* width, int* height);
+    status_t initTexture(Texture* texture, AssetManager& asset, const char* name,
+        bool premultiplyAlpha = true);
+    status_t initTexture(FileMap* map, int* width, int* height,
+        bool premultiplyAlpha = true);
     status_t initFont(Font* font, const char* fallback);
+    void initShaders();
     bool android();
     bool movie();
     void drawText(const char* str, const Font& font, bool bold, int* x, int* y);
@@ -173,6 +186,7 @@
     void drawProgress(int percent, const Font& font, const int xPos, const int yPos);
     void fadeFrame(int frameLeft, int frameBottom, int frameWidth, int frameHeight,
                    const Animation::Part& part, int fadedFramesCount);
+    void drawTexturedQuad(float xStart, float yStart, float width, float height);
     bool validClock(const Animation::Part& part);
     Animation* loadAnimation(const String8&);
     bool playAnimation(const Animation&);
@@ -192,6 +206,7 @@
     void checkExit();
 
     void handleViewport(nsecs_t timestep);
+    void initDynamicColors();
 
     sp<SurfaceComposerClient>       mSession;
     AssetManager mAssets;
@@ -218,6 +233,13 @@
     sp<TimeCheckThread> mTimeCheckThread = nullptr;
     sp<Callbacks> mCallbacks;
     Animation* mAnimation = nullptr;
+    GLuint mImageShader;
+    GLuint mTextShader;
+    GLuint mImageFadeLocation;
+    GLuint mImageTextureLocation;
+    GLuint mTextCropAreaLocation;
+    GLuint mTextTextureLocation;
+    GLuint mImageColorProgressLocation;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 4db1f71..1397f5e 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2721,10 +2721,13 @@
         // need to override their display in ResourcesManager.
         baseContext.mForceDisplayOverrideInResources = false;
         baseContext.mContextType = CONTEXT_TYPE_WINDOW_CONTEXT;
-        baseContext.mDisplay = display;
 
         final Resources windowContextResources = createWindowContextResources(baseContext);
         baseContext.setResources(windowContextResources);
+        // Associate the display with window context resources so that configuration update from
+        // the server side will also apply to the display's metrics.
+        baseContext.mDisplay = ResourcesManager.getInstance()
+                .getAdjustedDisplay(display.getDisplayId(), windowContextResources);
 
         return baseContext;
     }
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 5964f71..babeb08 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -135,6 +135,7 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
     /**
      * Returns a list of supported game modes for a given package.
      * <p>
@@ -151,4 +152,20 @@
         }
     }
 
+    /**
+     * Returns if ANGLE is enabled for a given package.
+     * <p>
+     * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
+     *
+     * @hide
+     */
+    @UserHandleAware
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+    public @GameMode boolean isAngleEnabled(@NonNull String packageName) {
+        try {
+            return mService.getAngleEnabled(packageName, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 4bf8a3f..189f0a2 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -23,4 +23,5 @@
     int getGameMode(String packageName, int userId);
     void setGameMode(String packageName, int gameMode, int userId);
     int[] getAvailableGameModes(String packageName);
+    boolean getAngleEnabled(String packageName, int userId);
 }
\ No newline at end of file
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4b054f4..719025f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1884,6 +1884,14 @@
              * clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the
              * activity itself.
              *
+             * <p>How an Action is displayed, including whether the {@code icon}, {@code text}, or
+             * both are displayed or required, depends on where and how the action is used, and the
+             * {@link Style} applied to the Notification.
+             *
+             * <p>When the {@code title} is a {@link android.text.Spanned}, any colors set by a
+             * {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed
+             * with an altered in luminance to ensure proper contrast within the Notification.
+             *
              * @param icon icon to show for this action
              * @param title the title of the action
              * @param intent the {@link PendingIntent} to fire when users trigger this action
@@ -5387,8 +5395,8 @@
             contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
             // Use different highlighted colors for conversations' unread count
             if (p.mHighlightExpander) {
-                pillColor = Colors.flattenAlpha(getPrimaryAccentColor(p), bgColor);
-                textColor = Colors.flattenAlpha(bgColor, pillColor);
+                pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor);
+                textColor = Colors.flattenAlpha(getColors(p).getOnAccentTextColor(), pillColor);
             }
             contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
             contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
@@ -6121,21 +6129,22 @@
             if (emphasizedMode) {
                 // change the background bgColor
                 CharSequence title = action.title;
-                ColorStateList[] outResultColor = new ColorStateList[1];
                 int buttonFillColor = getColors(p).getSecondaryAccentColor();
                 if (isLegacy()) {
                     title = ContrastColorUtil.clearColorSpans(title);
                 } else {
-                    int notifBackgroundColor = getColors(p).getBackgroundColor();
-                    title = ensureColorSpanContrast(title, notifBackgroundColor, outResultColor);
+                    // Check for a full-length span color to use as the button fill color.
+                    Integer fullLengthColor = getFullLengthSpanColor(title);
+                    if (fullLengthColor != null) {
+                        // Ensure the custom button fill has 1.3:1 contrast w/ notification bg.
+                        int notifBackgroundColor = getColors(p).getBackgroundColor();
+                        buttonFillColor = ensureButtonFillContrast(
+                                fullLengthColor, notifBackgroundColor);
+                    }
+                    // Remove full-length color spans and ensure text contrast with the button fill.
+                    title = ensureColorSpanContrast(title, buttonFillColor);
                 }
                 button.setTextViewText(R.id.action0, processTextSpans(title));
-                boolean hasColorOverride = outResultColor[0] != null;
-                if (hasColorOverride) {
-                    // There's a span spanning the full text, let's take it and use it as the
-                    // background color
-                    buttonFillColor = outResultColor[0].getDefaultColor();
-                }
                 final int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
                         buttonFillColor, mInNightMode);
                 button.setTextColor(R.id.action0, textColor);
@@ -6168,17 +6177,58 @@
         }
 
         /**
-         * Ensures contrast on color spans against a background color. also returns the color of the
-         * text if a span was found that spans over the whole text.
+         * Extract the color from a full-length span from the text.
+         *
+         * @param charSequence the charSequence containing spans
+         * @return the raw color of the text's last full-length span containing a color, or null if
+         * no full-length span sets the text color.
+         * @hide
+         */
+        @VisibleForTesting
+        @Nullable
+        public static Integer getFullLengthSpanColor(CharSequence charSequence) {
+            // NOTE: this method preserves the functionality that for a CharSequence with multiple
+            // full-length spans, the color of the last one is used.
+            Integer result = null;
+            if (charSequence instanceof Spanned) {
+                Spanned ss = (Spanned) charSequence;
+                Object[] spans = ss.getSpans(0, ss.length(), Object.class);
+                // First read through all full-length spans to get the button fill color, which will
+                //  be used as the background color for ensuring contrast of non-full-length spans.
+                for (Object span : spans) {
+                    int spanStart = ss.getSpanStart(span);
+                    int spanEnd = ss.getSpanEnd(span);
+                    boolean fullLength = (spanEnd - spanStart) == charSequence.length();
+                    if (!fullLength) {
+                        continue;
+                    }
+                    if (span instanceof TextAppearanceSpan) {
+                        TextAppearanceSpan originalSpan = (TextAppearanceSpan) span;
+                        ColorStateList textColor = originalSpan.getTextColor();
+                        if (textColor != null) {
+                            result = textColor.getDefaultColor();
+                        }
+                    } else if (span instanceof ForegroundColorSpan) {
+                        ForegroundColorSpan originalSpan = (ForegroundColorSpan) span;
+                        result = originalSpan.getForegroundColor();
+                    }
+                }
+            }
+            return result;
+        }
+
+        /**
+         * Ensures contrast on color spans against a background color.
+         * Note that any full-length color spans will be removed instead of being contrasted.
          *
          * @param charSequence the charSequence on which the spans are
          * @param background the background color to ensure the contrast against
-         * @param outResultColor an array in which a color will be returned as the first element if
-         *                    there exists a full length color span.
          * @return the contrasted charSequence
+         * @hide
          */
-        private static CharSequence ensureColorSpanContrast(CharSequence charSequence,
-                int background, ColorStateList[] outResultColor) {
+        @VisibleForTesting
+        public static CharSequence ensureColorSpanContrast(CharSequence charSequence,
+                int background) {
             if (charSequence instanceof Spanned) {
                 Spanned ss = (Spanned) charSequence;
                 Object[] spans = ss.getSpans(0, ss.length(), Object.class);
@@ -6195,19 +6245,19 @@
                         TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
                         ColorStateList textColor = originalSpan.getTextColor();
                         if (textColor != null) {
-                            int[] colors = textColor.getColors();
-                            int[] newColors = new int[colors.length];
-                            for (int i = 0; i < newColors.length; i++) {
-                                boolean isBgDark = isColorDark(background);
-                                newColors[i] = ContrastColorUtil.ensureLargeTextContrast(
-                                        colors[i], background, isBgDark);
-                            }
-                            textColor = new ColorStateList(textColor.getStates().clone(),
-                                    newColors);
                             if (fullLength) {
-                                outResultColor[0] = textColor;
                                 // Let's drop the color from the span
                                 textColor = null;
+                            } else {
+                                int[] colors = textColor.getColors();
+                                int[] newColors = new int[colors.length];
+                                for (int i = 0; i < newColors.length; i++) {
+                                    boolean isBgDark = isColorDark(background);
+                                    newColors[i] = ContrastColorUtil.ensureLargeTextContrast(
+                                            colors[i], background, isBgDark);
+                                }
+                                textColor = new ColorStateList(textColor.getStates().clone(),
+                                        newColors);
                             }
                             resultSpan = new TextAppearanceSpan(
                                     originalSpan.getFamily(),
@@ -6217,15 +6267,14 @@
                                     originalSpan.getLinkTextColor());
                         }
                     } else if (resultSpan instanceof ForegroundColorSpan) {
-                        ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
-                        int foregroundColor = originalSpan.getForegroundColor();
-                        boolean isBgDark = isColorDark(background);
-                        foregroundColor = ContrastColorUtil.ensureLargeTextContrast(
-                                foregroundColor, background, isBgDark);
                         if (fullLength) {
-                            outResultColor[0] = ColorStateList.valueOf(foregroundColor);
                             resultSpan = null;
                         } else {
+                            ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
+                            int foregroundColor = originalSpan.getForegroundColor();
+                            boolean isBgDark = isColorDark(background);
+                            foregroundColor = ContrastColorUtil.ensureLargeTextContrast(
+                                    foregroundColor, background, isBgDark);
                             resultSpan = new ForegroundColorSpan(foregroundColor);
                         }
                     } else {
@@ -6247,13 +6296,29 @@
          *
          * @param color the color to check
          * @return true if the color has higher contrast with white than black
+         * @hide
          */
-        private static boolean isColorDark(int color) {
+        public static boolean isColorDark(int color) {
             // as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint.
             return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474;
         }
 
         /**
+         * Finds a button fill color with sufficient contrast over bg (1.3:1) that has the same hue
+         * as the original color, but is lightened or darkened depending on whether the background
+         * is dark or light.
+         *
+         * @hide
+         */
+        @VisibleForTesting
+        public static int ensureButtonFillContrast(int color, int bg) {
+            return isColorDark(bg)
+                    ? ContrastColorUtil.findContrastColorAgainstDark(color, bg, true, 1.3)
+                    : ContrastColorUtil.findContrastColor(color, bg, true, 1.3);
+        }
+
+
+        /**
          * @return Whether we are currently building a notification from a legacy (an app that
          *         doesn't create material notifications by itself) app.
          */
@@ -12305,6 +12370,8 @@
         private int mSecondaryTextColor = COLOR_INVALID;
         private int mPrimaryAccentColor = COLOR_INVALID;
         private int mSecondaryAccentColor = COLOR_INVALID;
+        private int mTertiaryAccentColor = COLOR_INVALID;
+        private int mOnAccentTextColor = COLOR_INVALID;
         private int mErrorColor = COLOR_INVALID;
         private int mContrastColor = COLOR_INVALID;
         private int mRippleAlpha = 0x33;
@@ -12362,7 +12429,7 @@
 
             if (isColorized) {
                 if (rawColor == COLOR_DEFAULT) {
-                    int[] attrs = {R.attr.colorAccentTertiary};
+                    int[] attrs = {R.attr.colorAccentSecondary};
                     try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
                         mBackgroundColor = getColor(ta, 0, Color.WHITE);
                     }
@@ -12379,6 +12446,8 @@
                 mContrastColor = mPrimaryTextColor;
                 mPrimaryAccentColor = mPrimaryTextColor;
                 mSecondaryAccentColor = mSecondaryTextColor;
+                mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor);
+                mOnAccentTextColor = mBackgroundColor;
                 mErrorColor = mPrimaryTextColor;
                 mRippleAlpha = 0x33;
             } else {
@@ -12389,6 +12458,8 @@
                         R.attr.textColorSecondary,
                         R.attr.colorAccent,
                         R.attr.colorAccentSecondary,
+                        R.attr.colorAccentTertiary,
+                        R.attr.textColorOnAccent,
                         R.attr.colorError,
                         R.attr.colorControlHighlight
                 };
@@ -12399,8 +12470,10 @@
                     mSecondaryTextColor = getColor(ta, 3, COLOR_INVALID);
                     mPrimaryAccentColor = getColor(ta, 4, COLOR_INVALID);
                     mSecondaryAccentColor = getColor(ta, 5, COLOR_INVALID);
-                    mErrorColor = getColor(ta, 6, COLOR_INVALID);
-                    mRippleAlpha = Color.alpha(getColor(ta, 7, 0x33ffffff));
+                    mTertiaryAccentColor = getColor(ta, 6, COLOR_INVALID);
+                    mOnAccentTextColor = getColor(ta, 7, COLOR_INVALID);
+                    mErrorColor = getColor(ta, 8, COLOR_INVALID);
+                    mRippleAlpha = Color.alpha(getColor(ta, 9, 0x33ffffff));
                 }
                 mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor,
                         mBackgroundColor, nightMode);
@@ -12420,6 +12493,14 @@
                 if (mSecondaryAccentColor == COLOR_INVALID) {
                     mSecondaryAccentColor = mContrastColor;
                 }
+                if (mTertiaryAccentColor == COLOR_INVALID) {
+                    mTertiaryAccentColor = mContrastColor;
+                }
+                if (mOnAccentTextColor == COLOR_INVALID) {
+                    mOnAccentTextColor = ColorUtils.setAlphaComponent(
+                            ContrastColorUtil.resolvePrimaryColor(
+                                    ctx, mTertiaryAccentColor, nightMode), 0xFF);
+                }
                 if (mErrorColor == COLOR_INVALID) {
                     mErrorColor = mPrimaryTextColor;
                 }
@@ -12485,6 +12566,16 @@
             return mSecondaryAccentColor;
         }
 
+        /** @return the theme's tertiary accent color for colored UI elements. */
+        public @ColorInt int getTertiaryAccentColor() {
+            return mTertiaryAccentColor;
+        }
+
+        /** @return the theme's text color to be used on the tertiary accent color. */
+        public @ColorInt int getOnAccentTextColor() {
+            return mOnAccentTextColor;
+        }
+
         /**
          * @return the contrast-adjusted version of the color provided by the app, or the
          * primary text color when colorized.
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 1837fb8..6553b61 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -260,8 +260,6 @@
     private boolean mDemoted = false;
     private boolean mImportantConvo = false;
     private long mDeletedTime = DEFAULT_DELETION_TIME_MS;
-    // If the sound for this channel is missing, e.g. after restore.
-    private boolean mIsSoundMissing;
 
     /**
      * Creates a notification channel.
@@ -717,13 +715,6 @@
     }
 
     /**
-     * @hide
-     */
-    public boolean isSoundMissing() {
-        return mIsSoundMissing;
-    }
-
-    /**
      * Returns the audio attributes for sound played by notifications posted to this channel.
      */
     public AudioAttributes getAudioAttributes() {
@@ -1007,9 +998,8 @@
         // according to the docs because canonicalize method has to handle canonical uris as well.
         Uri canonicalizedUri = contentResolver.canonicalize(uri);
         if (canonicalizedUri == null) {
-            // We got a null because the uri in the backup does not exist here.
-            mIsSoundMissing = true;
-            return null;
+            // We got a null because the uri in the backup does not exist here, so we return default
+            return Settings.System.DEFAULT_NOTIFICATION_URI;
         }
         return contentResolver.uncanonicalize(canonicalizedUri);
     }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 8d332ab..6cfa39c 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -559,6 +559,53 @@
             return null;
         }
 
+        public Rect peekWallpaperDimensions(Context context, boolean returnDefault, int userId) {
+            if (mService != null) {
+                try {
+                    if (!mService.isWallpaperSupported(context.getOpPackageName())) {
+                        return new Rect();
+                    }
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+
+            Rect dimensions = null;
+            synchronized (this) {
+                try {
+                    Bundle params = new Bundle();
+                    // Let's peek user wallpaper first.
+                    ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
+                            context.getOpPackageName(), context.getAttributionTag(), this,
+                            FLAG_SYSTEM, params, userId);
+                    if (pfd != null) {
+                        BitmapFactory.Options options = new BitmapFactory.Options();
+                        options.inJustDecodeBounds = true;
+                        BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor(), null, options);
+                        dimensions = new Rect(0, 0, options.outWidth, options.outHeight);
+                    }
+                } catch (RemoteException ex) {
+                    Log.w(TAG, "peek wallpaper dimensions failed", ex);
+                }
+            }
+            // If user wallpaper is unavailable, may be the default one instead.
+            if ((dimensions == null || dimensions.width() == 0 || dimensions.height() == 0)
+                    && returnDefault) {
+                InputStream is = openDefaultWallpaper(context, FLAG_SYSTEM);
+                if (is != null) {
+                    try {
+                        BitmapFactory.Options options = new BitmapFactory.Options();
+                        options.inJustDecodeBounds = true;
+                        BitmapFactory.decodeStream(is, null, options);
+                        dimensions = new Rect(0, 0, options.outWidth, options.outHeight);
+                    } finally {
+                        IoUtils.closeQuietly(is);
+                    }
+                }
+            }
+            return dimensions;
+        }
+
         void forgetLoadedWallpaper() {
             synchronized (this) {
                 mCachedWallpaper = null;
@@ -1039,6 +1086,17 @@
     }
 
     /**
+     * Peek the dimensions of system wallpaper of the user without decoding it.
+     *
+     * @return the dimensions of system wallpaper
+     * @hide
+     */
+    public Rect peekBitmapDimensions() {
+        return sGlobals.peekWallpaperDimensions(
+                mContext, true /* returnDefault */, mContext.getUserId());
+    }
+
+    /**
      * Get an open, readable file descriptor to the given wallpaper image file.
      * The caller is responsible for closing the file descriptor when done ingesting the file.
      *
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index dae565e..67f631f 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -281,6 +281,32 @@
     }
 
     /**
+     * Convenience method for callers who need to indicate that some other package or
+     * some other user needs a backup pass. This can be useful in the case of groups of
+     * packages that share a uid and/or have user-specific data.
+     * <p>
+     * This method requires that the application hold the "android.permission.BACKUP"
+     * permission if the package named in the package argument does not run under the
+     * same uid as the caller. This method also requires that the application hold the
+     * "android.permission.INTERACT_ACROSS_USERS_FULL" if the user argument is not the
+     * same as the user the caller is running under.
+     * @param userId The user to back up
+     * @param packageName The package name identifying the application to back up.
+     *
+     * @hide
+     */
+    public static void dataChangedForUser(int userId, String packageName) {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                sService.dataChangedForUser(userId, packageName);
+            } catch (RemoteException e) {
+                Log.e(TAG, "dataChanged(userId,pkg) couldn't connect");
+            }
+        }
+    }
+
+    /**
      * @deprecated Applications shouldn't request a restore operation using this method. In Android
      * P and later, this method is a no-op.
      *
diff --git a/core/java/android/app/search/Query.java b/core/java/android/app/search/Query.java
index c64e107..f073b4e 100644
--- a/core/java/android/app/search/Query.java
+++ b/core/java/android/app/search/Query.java
@@ -70,7 +70,7 @@
             @NonNull Bundle extras) {
         mInput = input;
         mTimestampMillis = timestampMillis;
-        mExtras = extras == null ? extras : new Bundle();
+        mExtras = extras != null ? extras : new Bundle();
     }
 
     /**
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 3f3db29..c8c122d 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -438,9 +438,16 @@
     }
 
     private class OnAuthenticationCancelListener implements CancellationSignal.OnCancelListener {
+        private final long mAuthRequestId;
+
+        OnAuthenticationCancelListener(long id) {
+            mAuthRequestId = id;
+        }
+
         @Override
         public void onCancel() {
-            cancelAuthentication();
+            Log.d(TAG, "Cancel BP authentication requested for: " + mAuthRequestId);
+            cancelAuthentication(mAuthRequestId);
         }
     }
 
@@ -853,10 +860,12 @@
      * @param userId The user to authenticate
      * @param operationId The keystore operation associated with authentication
      *
+     * @return A requestId that can be used to cancel this operation.
+     *
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
-    public void authenticateUserForOperation(
+    public long authenticateUserForOperation(
             @NonNull CancellationSignal cancel,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AuthenticationCallback callback,
@@ -871,7 +880,8 @@
         if (callback == null) {
             throw new IllegalArgumentException("Must supply a callback");
         }
-        authenticateInternal(operationId, cancel, executor, callback, userId);
+
+        return authenticateInternal(operationId, cancel, executor, callback, userId);
     }
 
     /**
@@ -1002,10 +1012,10 @@
         authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId());
     }
 
-    private void cancelAuthentication() {
+    private void cancelAuthentication(long requestId) {
         if (mService != null) {
             try {
-                mService.cancelAuthentication(mToken, mContext.getOpPackageName());
+                mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
             } catch (RemoteException e) {
                 Log.e(TAG, "Unable to cancel authentication", e);
             }
@@ -1024,7 +1034,7 @@
         authenticateInternal(operationId, cancel, executor, callback, userId);
     }
 
-    private void authenticateInternal(
+    private long authenticateInternal(
             long operationId,
             @NonNull CancellationSignal cancel,
             @NonNull @CallbackExecutor Executor executor,
@@ -1040,9 +1050,7 @@
         try {
             if (cancel.isCanceled()) {
                 Log.w(TAG, "Authentication already canceled");
-                return;
-            } else {
-                cancel.setOnCancelListener(new OnAuthenticationCancelListener());
+                return -1;
             }
 
             mExecutor = executor;
@@ -1065,14 +1073,16 @@
                 promptInfo = mPromptInfo;
             }
 
-            mService.authenticate(mToken, operationId, userId, mBiometricServiceReceiver,
-                    mContext.getOpPackageName(), promptInfo);
-
+            final long authId = mService.authenticate(mToken, operationId, userId,
+                    mBiometricServiceReceiver, mContext.getOpPackageName(), promptInfo);
+            cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
+            return authId;
         } catch (RemoteException e) {
             Log.e(TAG, "Remote exception while authenticating", e);
             mExecutor.execute(() -> callback.onAuthenticationError(
                     BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE,
                     mContext.getString(R.string.biometric_error_hw_unavailable)));
+            return -1;
         }
     }
 
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 4c2a9ae..91f794c 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -41,13 +41,14 @@
     // Retrieve the package where BIometricOrompt's UI is implemented
     String getUiPackage();
 
-    // Requests authentication. The service choose the appropriate biometric to use, and show
-    // the corresponding BiometricDialog.
-    void authenticate(IBinder token, long sessionId, int userId,
+    // Requests authentication. The service chooses the appropriate biometric to use, and shows
+    // the corresponding BiometricDialog. A requestId is returned that can be used to cancel
+    // this operation.
+    long authenticate(IBinder token, long sessionId, int userId,
             IBiometricServiceReceiver receiver, String opPackageName, in PromptInfo promptInfo);
 
-    // Cancel authentication for the given sessionId
-    void cancelAuthentication(IBinder token, String opPackageName);
+    // Cancel authentication for the given requestId.
+    void cancelAuthentication(IBinder token, String opPackageName, long requestId);
 
     // TODO(b/141025588): Make userId the first arg to be consistent with hasEnrolledBiometrics.
     // Checks if biometrics can be used.
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
index 876513f..addd622 100644
--- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -48,13 +48,13 @@
     // startPreparedClient().
     void prepareForAuthentication(boolean requireConfirmation, IBinder token, long operationId,
             int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
-            int cookie, boolean allowBackgroundAuthentication);
+            long requestId, int cookie, boolean allowBackgroundAuthentication);
 
     // Starts authentication with the previously prepared client.
     void startPreparedClient(int cookie);
 
-    // Cancels authentication.
-    void cancelAuthenticationFromService(IBinder token, String opPackageName);
+    // Cancels authentication for the given requestId.
+    void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId);
 
     // Determine if HAL is loaded and ready
     boolean isHardwareDetected(String opPackageName);
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 64b5118..2c3c8c3 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -36,13 +36,14 @@
     // Retrieve static sensor properties for all biometric sensors
     List<SensorPropertiesInternal> getSensorProperties(String opPackageName);
 
-    // Requests authentication. The service choose the appropriate biometric to use, and show
-    // the corresponding BiometricDialog.
-    void authenticate(IBinder token, long operationId, int userId,
+    // Requests authentication. The service chooses the appropriate biometric to use, and shows
+    // the corresponding BiometricDialog. A requestId is returned that can be used to cancel
+    // this operation.
+    long authenticate(IBinder token, long operationId, int userId,
             IBiometricServiceReceiver receiver, String opPackageName, in PromptInfo promptInfo);
 
-    // Cancel authentication for the given session.
-    void cancelAuthentication(IBinder token, String opPackageName);
+    // Cancel authentication for the given requestId.
+    void cancelAuthentication(IBinder token, String opPackageName, long requestId);
 
     // Checks if biometrics can be used.
     int canAuthenticate(String opPackageName, int userId, int callingUserId, int authenticators);
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index fc728a2..4708f3e 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -104,6 +104,9 @@
     private SparseArray<CaptureCallbackHolder> mCaptureCallbackMap =
             new SparseArray<CaptureCallbackHolder>();
 
+    /** map request IDs which have batchedOutputs to requestCount*/
+    private HashMap<Integer, Integer> mBatchOutputMap = new HashMap<>();
+
     private int mRepeatingRequestId = REQUEST_ID_NONE;
     // Latest repeating request list's types
     private int[] mRepeatingRequestTypes;
@@ -973,6 +976,7 @@
             mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(REQUEST_ID_NONE, null);
             mIdle = true;
             mCaptureCallbackMap = new SparseArray<CaptureCallbackHolder>();
+            mBatchOutputMap = new HashMap<>();
             mFrameNumberTracker = new FrameNumberTracker();
 
             mCurrentSession.closeWithoutDraining();
@@ -1179,6 +1183,41 @@
         return requestTypes;
     }
 
+    private boolean hasBatchedOutputs(List<CaptureRequest> requestList) {
+        boolean hasBatchedOutputs = true;
+        for (int i = 0; i < requestList.size(); i++) {
+            CaptureRequest request = requestList.get(i);
+            if (!request.isPartOfCRequestList()) {
+                hasBatchedOutputs = false;
+                break;
+            }
+            if (i == 0) {
+                Collection<Surface> targets = request.getTargets();
+                if (targets.size() != 2) {
+                    hasBatchedOutputs = false;
+                    break;
+                }
+            }
+        }
+        return hasBatchedOutputs;
+    }
+
+    private void updateTracker(int requestId, long frameNumber,
+            int requestType, CaptureResult result, boolean isPartialResult) {
+        int requestCount = 1;
+        // If the request has batchedOutputs update each frame within the batch.
+        if (mBatchOutputMap.containsKey(requestId)) {
+            requestCount = mBatchOutputMap.get(requestId);
+            for (int i = 0; i < requestCount; i++) {
+                mFrameNumberTracker.updateTracker(frameNumber - (requestCount - 1 - i),
+                        result, isPartialResult, requestType);
+            }
+        } else {
+            mFrameNumberTracker.updateTracker(frameNumber, result,
+                    isPartialResult, requestType);
+        }
+    }
+
     private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback,
             Executor executor, boolean repeating) throws CameraAccessException {
 
@@ -1224,6 +1263,14 @@
                 request.recoverStreamIdToSurface();
             }
 
+            // If the request has batched outputs, then store the
+            // requestCount and requestId in the map.
+            boolean hasBatchedOutputs = hasBatchedOutputs(requestList);
+            if (hasBatchedOutputs) {
+                int requestCount = requestList.size();
+                mBatchOutputMap.put(requestInfo.getRequestId(), requestCount);
+            }
+
             if (callback != null) {
                 mCaptureCallbackMap.put(requestInfo.getRequestId(),
                         new CaptureCallbackHolder(
@@ -1839,8 +1886,18 @@
             if (DEBUG) {
                 Log.v(TAG, String.format("got error frame %d", frameNumber));
             }
-            mFrameNumberTracker.updateTracker(frameNumber,
-                    /*error*/true, request.getRequestType());
+
+            // Update FrameNumberTracker for every frame during HFR mode.
+            if (mBatchOutputMap.containsKey(requestId)) {
+                for (int i = 0; i < mBatchOutputMap.get(requestId); i++) {
+                    mFrameNumberTracker.updateTracker(frameNumber - (subsequenceId - i),
+                            /*error*/true, request.getRequestType());
+                }
+            } else {
+                mFrameNumberTracker.updateTracker(frameNumber,
+                        /*error*/true, request.getRequestType());
+            }
+
             checkAndFireSequenceComplete();
 
             // Dispatch the failure callback
@@ -2023,7 +2080,6 @@
         public void onResultReceived(CameraMetadataNative result,
                 CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])
                 throws RemoteException {
-
             int requestId = resultExtras.getRequestId();
             long frameNumber = resultExtras.getFrameNumber();
 
@@ -2064,8 +2120,8 @@
                                         + frameNumber);
                     }
 
-                    mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
-                            requestType);
+                    updateTracker(requestId, frameNumber, requestType, /*result*/null,
+                            isPartialResult);
 
                     return;
                 }
@@ -2077,8 +2133,9 @@
                                         + frameNumber);
                     }
 
-                    mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
-                            requestType);
+                    updateTracker(requestId, frameNumber, requestType, /*result*/null,
+                            isPartialResult);
+
                     return;
                 }
 
@@ -2184,9 +2241,7 @@
                     Binder.restoreCallingIdentity(ident);
                 }
 
-                // Collect the partials for a total result; or mark the frame as totally completed
-                mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult,
-                        requestType);
+                updateTracker(requestId, frameNumber, requestType, finalResult, isPartialResult);
 
                 // Fire onCaptureSequenceCompleted
                 if (!isPartialResult) {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index a9b95fc..6c39365 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -361,6 +361,11 @@
         for (int i = 0; i < numListeners; i++) {
             mask |= mDisplayListeners.get(i).mEventsMask;
         }
+        if (mDispatchNativeCallbacks) {
+            mask |= DisplayManager.EVENT_FLAG_DISPLAY_ADDED
+                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+        }
         return mask;
     }
 
@@ -1047,12 +1052,17 @@
 
     private static native void nSignalNativeCallbacks(float refreshRate);
 
-    // Called from AChoreographer via JNI.
-    // Registers AChoreographer so that refresh rate callbacks can be dispatched from DMS.
-    private void registerNativeChoreographerForRefreshRateCallbacks() {
+    /**
+     * Called from AChoreographer via JNI.
+     * Registers AChoreographer so that refresh rate callbacks can be dispatched from DMS.
+     * Public for unit testing to be able to call this method.
+     */
+    @VisibleForTesting
+    public void registerNativeChoreographerForRefreshRateCallbacks() {
         synchronized (mLock) {
-            registerCallbackIfNeededLocked();
             mDispatchNativeCallbacks = true;
+            registerCallbackIfNeededLocked();
+            updateCallbackIfNeededLocked();
             DisplayInfo display = getDisplayInfoLocked(Display.DEFAULT_DISPLAY);
             if (display != null) {
                 // We need to tell AChoreographer instances the current refresh rate so that apps
@@ -1063,11 +1073,16 @@
         }
     }
 
-    // Called from AChoreographer via JNI.
-    // Unregisters AChoreographer from receiving refresh rate callbacks.
-    private void unregisterNativeChoreographerForRefreshRateCallbacks() {
+    /**
+     * Called from AChoreographer via JNI.
+     * Unregisters AChoreographer from receiving refresh rate callbacks.
+     * Public for unit testing to be able to call this method.
+     */
+    @VisibleForTesting
+    public void unregisterNativeChoreographerForRefreshRateCallbacks() {
         synchronized (mLock) {
             mDispatchNativeCallbacks = false;
+            updateCallbackIfNeededLocked();
         }
     }
 }
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 385ad2d..56f8142 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -58,7 +58,7 @@
 public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants {
 
     private static final String TAG = "FaceManager";
-    private static final boolean DEBUG = true;
+
     private static final int MSG_ENROLL_RESULT = 100;
     private static final int MSG_ACQUIRED = 101;
     private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
@@ -207,13 +207,9 @@
             throw new IllegalArgumentException("Must supply an authentication callback");
         }
 
-        if (cancel != null) {
-            if (cancel.isCanceled()) {
-                Slog.w(TAG, "authentication already canceled");
-                return;
-            } else {
-                cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
-            }
+        if (cancel != null && cancel.isCanceled()) {
+            Slog.w(TAG, "authentication already canceled");
+            return;
         }
 
         if (mService != null) {
@@ -223,17 +219,18 @@
                 mCryptoObject = crypto;
                 final long operationId = crypto != null ? crypto.getOpId() : 0;
                 Trace.beginSection("FaceManager#authenticate");
-                mService.authenticate(mToken, operationId, userId, mServiceReceiver,
-                        mContext.getOpPackageName(), isKeyguardBypassEnabled);
+                final long authId = mService.authenticate(mToken, operationId, userId,
+                        mServiceReceiver, mContext.getOpPackageName(), isKeyguardBypassEnabled);
+                if (cancel != null) {
+                    cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
+                }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception while authenticating: ", e);
-                if (callback != null) {
-                    // Though this may not be a hardware issue, it will cause apps to give up or
-                    // try again later.
-                    callback.onAuthenticationError(FACE_ERROR_HW_UNAVAILABLE,
-                            getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
-                                    0 /* vendorCode */));
-                }
+                // Though this may not be a hardware issue, it will cause apps to give up or
+                // try again later.
+                callback.onAuthenticationError(FACE_ERROR_HW_UNAVAILABLE,
+                        getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
+                                0 /* vendorCode */));
             } finally {
                 Trace.endSection();
             }
@@ -255,14 +252,14 @@
         if (cancel.isCanceled()) {
             Slog.w(TAG, "Detection already cancelled");
             return;
-        } else {
-            cancel.setOnCancelListener(new OnFaceDetectionCancelListener());
         }
 
         mFaceDetectionCallback = callback;
 
         try {
-            mService.detectFace(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+            final long authId = mService.detectFace(
+                    mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+            cancel.setOnCancelListener(new OnFaceDetectionCancelListener(authId));
         } catch (RemoteException e) {
             Slog.w(TAG, "Remote exception when requesting finger detect", e);
         }
@@ -726,23 +723,23 @@
         }
     }
 
-    private void cancelAuthentication(CryptoObject cryptoObject) {
+    private void cancelAuthentication(long requestId) {
         if (mService != null) {
             try {
-                mService.cancelAuthentication(mToken, mContext.getOpPackageName());
+                mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
         }
     }
 
-    private void cancelFaceDetect() {
+    private void cancelFaceDetect(long requestId) {
         if (mService == null) {
             return;
         }
 
         try {
-            mService.cancelFaceDetect(mToken, mContext.getOpPackageName());
+            mService.cancelFaceDetect(mToken, mContext.getOpPackageName(), requestId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -794,9 +791,9 @@
         // This is used as a last resort in case a vendor string is missing
         // It should not happen for anything other than FACE_ERROR_VENDOR, but
         // warn and use the default if all else fails.
-        // TODO(b/196639965): update string
         Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
-        return "";
+        return context.getString(
+                com.android.internal.R.string.face_error_vendor_unknown);
     }
 
     /**
@@ -1110,22 +1107,30 @@
     }
 
     private class OnAuthenticationCancelListener implements OnCancelListener {
-        private final CryptoObject mCrypto;
+        private final long mAuthRequestId;
 
-        OnAuthenticationCancelListener(CryptoObject crypto) {
-            mCrypto = crypto;
+        OnAuthenticationCancelListener(long id) {
+            mAuthRequestId = id;
         }
 
         @Override
         public void onCancel() {
-            cancelAuthentication(mCrypto);
+            Slog.d(TAG, "Cancel face authentication requested for: " + mAuthRequestId);
+            cancelAuthentication(mAuthRequestId);
         }
     }
 
     private class OnFaceDetectionCancelListener implements OnCancelListener {
+        private final long mAuthRequestId;
+
+        OnFaceDetectionCancelListener(long id) {
+            mAuthRequestId = id;
+        }
+
         @Override
         public void onCancel() {
-            cancelFaceDetect();
+            Slog.d(TAG, "Cancel face detect requested for: " + mAuthRequestId);
+            cancelFaceDetect(mAuthRequestId);
         }
     }
 
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index db02a0ef..e919824 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -44,34 +44,36 @@
     // Retrieve static sensor properties for the specified sensor
     FaceSensorPropertiesInternal getSensorProperties(int sensorId, String opPackageName);
 
-    // Authenticate the given sessionId with a face
-    void authenticate(IBinder token, long operationId, int userId, IFaceServiceReceiver receiver,
+    // Authenticate with a face. A requestId is returned that can be used to cancel this operation.
+    long authenticate(IBinder token, long operationId, int userId, IFaceServiceReceiver receiver,
             String opPackageName, boolean isKeyguardBypassEnabled);
 
     // Uses the face hardware to detect for the presence of a face, without giving details
-    // about accept/reject/lockout.
-    void detectFace(IBinder token, int userId, IFaceServiceReceiver receiver, String opPackageName);
+    // about accept/reject/lockout. A requestId is returned that can be used to cancel this
+    // operation.
+    long detectFace(IBinder token, int userId, IFaceServiceReceiver receiver, String opPackageName);
 
     // This method prepares the service to start authenticating, but doesn't start authentication.
     // This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be
     // called from BiometricService. The additional uid, pid, userId arguments should be determined
     // by BiometricService. To start authentication after the clients are ready, use
     // startPreparedClient().
-    void prepareForAuthentication(int sensorId, boolean requireConfirmation, IBinder token, long operationId,
-            int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
-            int cookie, boolean allowBackgroundAuthentication);
+    void prepareForAuthentication(int sensorId, boolean requireConfirmation, IBinder token,
+            long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
+            String opPackageName, long requestId, int cookie,
+            boolean allowBackgroundAuthentication);
 
     // Starts authentication with the previously prepared client.
     void startPreparedClient(int sensorId, int cookie);
 
-    // Cancel authentication for the given sessionId
-    void cancelAuthentication(IBinder token, String opPackageName);
+    // Cancel authentication for the given requestId.
+    void cancelAuthentication(IBinder token, String opPackageName, long requestId);
 
-    // Cancel face detection
-    void cancelFaceDetect(IBinder token, String opPackageName);
+    // Cancel face detection for the given requestId.
+    void cancelFaceDetect(IBinder token, String opPackageName, long requestId);
 
     // Same as above, with extra arguments.
-    void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName);
+    void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
 
     // Start face enrollment
     void enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 87d45b9..d48d562 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -189,22 +189,30 @@
     }
 
     private class OnAuthenticationCancelListener implements OnCancelListener {
-        private android.hardware.biometrics.CryptoObject mCrypto;
+        private final long mAuthRequestId;
 
-        public OnAuthenticationCancelListener(android.hardware.biometrics.CryptoObject crypto) {
-            mCrypto = crypto;
+        OnAuthenticationCancelListener(long id) {
+            mAuthRequestId = id;
         }
 
         @Override
         public void onCancel() {
-            cancelAuthentication(mCrypto);
+            Slog.d(TAG, "Cancel fingerprint authentication requested for: " + mAuthRequestId);
+            cancelAuthentication(mAuthRequestId);
         }
     }
 
     private class OnFingerprintDetectionCancelListener implements OnCancelListener {
+        private final long mAuthRequestId;
+
+        OnFingerprintDetectionCancelListener(long id) {
+            mAuthRequestId = id;
+        }
+
         @Override
         public void onCancel() {
-            cancelFingerprintDetect();
+            Slog.d(TAG, "Cancel fingerprint detect requested for: " + mAuthRequestId);
+            cancelFingerprintDetect(mAuthRequestId);
         }
     }
 
@@ -552,13 +560,9 @@
             throw new IllegalArgumentException("Must supply an authentication callback");
         }
 
-        if (cancel != null) {
-            if (cancel.isCanceled()) {
-                Slog.w(TAG, "authentication already canceled");
-                return;
-            } else {
-                cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
-            }
+        if (cancel != null && cancel.isCanceled()) {
+            Slog.w(TAG, "authentication already canceled");
+            return;
         }
 
         if (mService != null) {
@@ -567,8 +571,11 @@
                 mAuthenticationCallback = callback;
                 mCryptoObject = crypto;
                 final long operationId = crypto != null ? crypto.getOpId() : 0;
-                mService.authenticate(mToken, operationId, sensorId, userId, mServiceReceiver,
-                        mContext.getOpPackageName());
+                final long authId = mService.authenticate(mToken, operationId, sensorId, userId,
+                        mServiceReceiver, mContext.getOpPackageName());
+                if (cancel != null) {
+                    cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
+                }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception while authenticating: ", e);
                 // Though this may not be a hardware issue, it will cause apps to give up or try
@@ -595,15 +602,14 @@
         if (cancel.isCanceled()) {
             Slog.w(TAG, "Detection already cancelled");
             return;
-        } else {
-            cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener());
         }
 
         mFingerprintDetectionCallback = callback;
 
         try {
-            mService.detectFingerprint(mToken, userId, mServiceReceiver,
+            final long authId = mService.detectFingerprint(mToken, userId, mServiceReceiver,
                     mContext.getOpPackageName());
+            cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener(authId));
         } catch (RemoteException e) {
             Slog.w(TAG, "Remote exception when requesting finger detect", e);
         }
@@ -844,26 +850,6 @@
     }
 
     /**
-     * Checks if the specified user has enrollments in any of the specified sensors.
-     * @hide
-     */
-    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
-    public boolean hasEnrolledTemplatesForAnySensor(int userId,
-            @NonNull List<FingerprintSensorPropertiesInternal> sensors) {
-        if (mService == null) {
-            Slog.w(TAG, "hasEnrolledTemplatesForAnySensor: no fingerprint service");
-            return false;
-        }
-
-        try {
-            return mService.hasEnrolledTemplatesForAnySensor(userId, sensors,
-                    mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -1320,21 +1306,21 @@
         }
     }
 
-    private void cancelAuthentication(android.hardware.biometrics.CryptoObject cryptoObject) {
+    private void cancelAuthentication(long requestId) {
         if (mService != null) try {
-            mService.cancelAuthentication(mToken, mContext.getOpPackageName());
+            mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
-    private void cancelFingerprintDetect() {
+    private void cancelFingerprintDetect(long requestId) {
         if (mService == null) {
             return;
         }
 
         try {
-            mService.cancelFingerprintDetect(mToken, mContext.getOpPackageName());
+            mService.cancelFingerprintDetect(mToken, mContext.getOpPackageName(), requestId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1390,9 +1376,9 @@
         // This is used as a last resort in case a vendor string is missing
         // It should not happen for anything other than FINGERPRINT_ERROR_VENDOR, but
         // warn and use the default if all else fails.
-        // TODO(b/196639965): update string
         Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
-        return "";
+        return context.getString(
+                com.android.internal.R.string.fingerprint_error_vendor_unknown);
     }
 
     /**
diff --git a/core/java/android/hardware/fingerprint/FingerprintStateListener.java b/core/java/android/hardware/fingerprint/FingerprintStateListener.java
index 6e607a2..cf914c5 100644
--- a/core/java/android/hardware/fingerprint/FingerprintStateListener.java
+++ b/core/java/android/hardware/fingerprint/FingerprintStateListener.java
@@ -49,5 +49,10 @@
      * Defines behavior in response to state update
      * @param newState new state of fingerprint sensor
      */
-    public abstract void onStateChanged(@FingerprintStateListener.State int newState);
+    public void onStateChanged(@FingerprintStateListener.State int newState) {};
+
+    /**
+     * Invoked when enrollment state changes for the specified user
+     */
+    public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {};
 }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 3979afe..de94b2f 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -48,15 +48,16 @@
     // Retrieve static sensor properties for the specified sensor
     FingerprintSensorPropertiesInternal getSensorProperties(int sensorId, String opPackageName);
 
-    // Authenticate the given sessionId with a fingerprint. This is protected by
-    // USE_FINGERPRINT/USE_BIOMETRIC permission. This is effectively deprecated, since it only comes
-    // through FingerprintManager now.
-    void authenticate(IBinder token, long operationId, int sensorId, int userId,
+    // Authenticate with a fingerprint. This is protected by USE_FINGERPRINT/USE_BIOMETRIC
+    // permission. This is effectively deprecated, since it only comes through FingerprintManager
+    // now. A requestId is returned that can be used to cancel this operation.
+    long authenticate(IBinder token, long operationId, int sensorId, int userId,
             IFingerprintServiceReceiver receiver, String opPackageName);
 
     // Uses the fingerprint hardware to detect for the presence of a finger, without giving details
-    // about accept/reject/lockout.
-    void detectFingerprint(IBinder token, int userId, IFingerprintServiceReceiver receiver,
+    // about accept/reject/lockout. A requestId is returned that can be used to cancel this
+    // operation.
+    long detectFingerprint(IBinder token, int userId, IFingerprintServiceReceiver receiver,
             String opPackageName);
 
     // This method prepares the service to start authenticating, but doesn't start authentication.
@@ -65,21 +66,21 @@
     // by BiometricService. To start authentication after the clients are ready, use
     // startPreparedClient().
     void prepareForAuthentication(int sensorId, IBinder token, long operationId, int userId,
-            IBiometricSensorReceiver sensorReceiver, String opPackageName, int cookie,
-            boolean allowBackgroundAuthentication);
+            IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
+            int cookie, boolean allowBackgroundAuthentication);
 
     // Starts authentication with the previously prepared client.
     void startPreparedClient(int sensorId, int cookie);
 
-    // Cancel authentication for the given sessionId
-    void cancelAuthentication(IBinder token, String opPackageName);
+    // Cancel authentication for the given requestId.
+    void cancelAuthentication(IBinder token, String opPackageName, long requestId);
 
-    // Cancel finger detection
-    void cancelFingerprintDetect(IBinder token, String opPackageName);
+    // Cancel finger detection for the given requestId.
+    void cancelFingerprintDetect(IBinder token, String opPackageName, long requestId);
 
     // Same as above, except this is protected by the MANAGE_BIOMETRIC signature permission. Takes
     // an additional uid, pid, userid.
-    void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName);
+    void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
 
     // Start fingerprint enrollment
     void enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
@@ -119,9 +120,6 @@
     // Determine if a user has at least one enrolled fingerprint.
     boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName);
 
-    // Determine if a user has at least one enrolled fingerprint in any of the specified sensors
-    boolean hasEnrolledTemplatesForAnySensor(int userId, in List<FingerprintSensorPropertiesInternal> sensors, String opPackageName);
-
     // Return the LockoutTracker status for the specified user
     int getLockoutModeForUser(int sensorId, int userId);
 
diff --git a/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl b/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
index 56dba7e..1aa6fa1 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
@@ -24,4 +24,5 @@
  */
 oneway interface IFingerprintStateListener {
     void onStateChanged(int newState);
+    void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments);
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 881e0cf..74cb42d 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1749,12 +1749,12 @@
         if (config.orientation != Configuration.ORIENTATION_LANDSCAPE) {
             return false;
         }
-        if ((mInputEditorInfo != null
-                && (mInputEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN) != 0)
+        if (mInputEditorInfo != null
+                && ((mInputEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN) != 0
                 // If app window has portrait orientation, regardless of what display orientation
                 // is, IME shouldn't use fullscreen-mode.
                 || (mInputEditorInfo.internalImeOptions
-                        & EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT) != 0) {
+                        & EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT) != 0)) {
             return false;
         }
         return true;
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index be21fea..1cceea3 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.app.Activity;
+import android.app.GameManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -26,8 +27,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.AssetManager;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
@@ -37,9 +36,6 @@
 
 import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.util.ArrayList;
@@ -88,9 +84,6 @@
     private static final String UPDATABLE_DRIVER_ALLOWLIST_ALL = "*";
     private static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt";
 
-    // ANGLE related properties.
-    private static final String ANGLE_RULES_FILE = "a4a_rules.json";
-    private static final String ANGLE_TEMP_RULES = "debug.angle.rules";
     private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID";
     private static final String ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE =
             "android.app.action.ANGLE_FOR_ANDROID_TOAST_MESSAGE";
@@ -121,6 +114,7 @@
     private ClassLoader mClassLoader;
     private String mLibrarySearchPaths;
     private String mLibraryPermittedPaths;
+    private GameManager mGameManager;
 
     private int mAngleOptInIndex = -1;
 
@@ -133,6 +127,8 @@
         final ApplicationInfo appInfoWithMetaData =
                 getAppInfoWithMetadata(context, pm, packageName);
 
+        mGameManager = context.getSystemService(GameManager.class);
+
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
         setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
@@ -151,6 +147,23 @@
     }
 
     /**
+     * Query to determine if the Game Mode has enabled ANGLE.
+     */
+    private boolean isAngleEnabledByGameMode(Context context, String packageName) {
+        try {
+            final boolean gameModeEnabledAngle =
+                    (mGameManager != null) && mGameManager.isAngleEnabled(packageName);
+            Log.v(TAG, "ANGLE GameManagerService for " + packageName + ": " + gameModeEnabledAngle);
+            return gameModeEnabledAngle;
+        } catch (SecurityException e) {
+            Log.e(TAG, "Caught exception while querying GameManagerService if ANGLE is enabled "
+                    + "for package: " + packageName);
+        }
+
+        return false;
+    }
+
+    /**
      * Query to determine if ANGLE should be used
      */
     private boolean shouldUseAngle(Context context, Bundle coreSettings,
@@ -164,21 +177,16 @@
         Log.v(TAG, "ANGLE Developer option for '" + packageName + "' "
                 + "set to: '" + devOptIn + "'");
 
-        // We only want to use ANGLE if the app is in the allowlist, or the developer has
-        // explicitly chosen something other than default driver.
-        // The allowlist will be generated by the ANGLE APK at both boot time and
-        // ANGLE update time. It will only include apps mentioned in the rules file.
-        final boolean allowed = checkAngleAllowlist(context, coreSettings, packageName);
+        // We only want to use ANGLE if the developer has explicitly chosen something other than
+        // default driver.
         final boolean requested = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE);
-
-        if (allowed) {
-            Log.v(TAG, "ANGLE allowlist includes " + packageName);
-        }
         if (requested) {
             Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
         }
 
-        return allowed || requested;
+        final boolean gameModeEnabledAngle = isAngleEnabledByGameMode(context, packageName);
+
+        return requested || gameModeEnabledAngle;
     }
 
     private int getVulkanVersion(PackageManager pm) {
@@ -475,117 +483,6 @@
     }
 
     /**
-     * Attempt to setup ANGLE with a temporary rules file.
-     * True: Temporary rules file was loaded.
-     * False: Temporary rules file was *not* loaded.
-     */
-    private boolean setupAngleWithTempRulesFile(Context context,
-                                                String packageName,
-                                                String paths,
-                                                String devOptIn) {
-        /**
-         * We only want to load a temp rules file for:
-         *  - apps that are marked 'debuggable' in their manifest
-         *  - devices that are running a userdebug build (ro.debuggable) or can inject libraries for
-         *    debugging (PR_SET_DUMPABLE).
-         */
-        if (!isDebuggable()) {
-            Log.v(TAG, "Skipping loading temporary rules file");
-            return false;
-        }
-
-        final String angleTempRules = SystemProperties.get(ANGLE_TEMP_RULES);
-
-        if (TextUtils.isEmpty(angleTempRules)) {
-            Log.v(TAG, "System property '" + ANGLE_TEMP_RULES + "' is not set or is empty");
-            return false;
-        }
-
-        Log.i(TAG, "Detected system property " + ANGLE_TEMP_RULES + ": " + angleTempRules);
-
-        final File tempRulesFile = new File(angleTempRules);
-        if (tempRulesFile.exists()) {
-            Log.i(TAG, angleTempRules + " exists, loading file.");
-            try {
-                final FileInputStream stream = new FileInputStream(angleTempRules);
-
-                try {
-                    final FileDescriptor rulesFd = stream.getFD();
-                    final long rulesOffset = 0;
-                    final long rulesLength = stream.getChannel().size();
-                    Log.i(TAG, "Loaded temporary ANGLE rules from " + angleTempRules);
-
-                    setAngleInfo(paths, packageName, devOptIn, null,
-                            rulesFd, rulesOffset, rulesLength);
-
-                    stream.close();
-
-                    // We successfully setup ANGLE, so return with good status
-                    return true;
-                } catch (IOException e) {
-                    Log.w(TAG, "Hit IOException thrown by FileInputStream: " + e);
-                }
-            } catch (FileNotFoundException e) {
-                Log.w(TAG, "Temp ANGLE rules file not found: " + e);
-            } catch (SecurityException e) {
-                Log.w(TAG, "Temp ANGLE rules file not accessible: " + e);
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Attempt to setup ANGLE with a rules file loaded from the ANGLE APK.
-     * True: APK rules file was loaded.
-     * False: APK rules file was *not* loaded.
-     */
-    private boolean setupAngleRulesApk(String anglePkgName,
-            ApplicationInfo angleInfo,
-            PackageManager pm,
-            String packageName,
-            String paths,
-            String devOptIn,
-            String[] features) {
-        // Pass the rules file to loader for ANGLE decisions
-        try {
-            final AssetManager angleAssets = pm.getResourcesForApplication(angleInfo).getAssets();
-
-            try {
-                final AssetFileDescriptor assetsFd = angleAssets.openFd(ANGLE_RULES_FILE);
-
-                setAngleInfo(paths, packageName, devOptIn, features, assetsFd.getFileDescriptor(),
-                        assetsFd.getStartOffset(), assetsFd.getLength());
-
-                assetsFd.close();
-
-                return true;
-            } catch (IOException e) {
-                Log.w(TAG, "Failed to get AssetFileDescriptor for " + ANGLE_RULES_FILE
-                        + " from '" + anglePkgName + "': " + e);
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.w(TAG, "Failed to get AssetManager for '" + anglePkgName + "': " + e);
-        }
-
-        return false;
-    }
-
-    /**
-     * Pull ANGLE allowlist from GlobalSettings and compare against current package
-     */
-    private boolean checkAngleAllowlist(Context context, Bundle bundle, String packageName) {
-        final ContentResolver contentResolver = context.getContentResolver();
-        final List<String> angleAllowlist =
-                getGlobalSettingsString(contentResolver, bundle,
-                    Settings.Global.ANGLE_ALLOWLIST);
-
-        if (DEBUG) Log.v(TAG, "ANGLE allowlist: " + angleAllowlist);
-
-        return angleAllowlist.contains(packageName);
-    }
-
-    /**
      * Pass ANGLE details down to trigger enable logic
      *
      * @param context
@@ -647,28 +544,21 @@
 
         if (DEBUG) Log.v(TAG, "ANGLE package libs: " + paths);
 
-        // If the user has set the developer option to something other than default,
-        // we need to call setupAngleRulesApk() with the package name and the developer
-        // option value (native/angle/other). Then later when we are actually trying to
-        // load a driver, GraphicsEnv::getShouldUseAngle() has seen the package name before
-        // and can confidently answer yes/no based on the previously set developer
-        // option value.
-        final String devOptIn = getDriverForPackage(context, bundle, packageName);
-
-        if (setupAngleWithTempRulesFile(context, packageName, paths, devOptIn)) {
-            // We setup ANGLE with a temp rules file, so we're done here.
-            return true;
+        // We need to call setAngleInfo() with the package name and the developer option value
+        //(native/angle/other). Then later when we are actually trying to load a driver,
+        //GraphicsEnv::getShouldUseAngle() has seen the package name before and can confidently
+        //answer yes/no based on the previously set developer option value.
+        final String devOptIn;
+        final String[] features = getAngleEglFeatures(context, bundle);
+        final boolean gameModeEnabledAngle = isAngleEnabledByGameMode(context, packageName);
+        if (gameModeEnabledAngle) {
+            devOptIn = ANGLE_GL_DRIVER_CHOICE_ANGLE;
+        } else {
+            devOptIn = getDriverForPackage(context, bundle, packageName);
         }
+        setAngleInfo(paths, packageName, devOptIn, features);
 
-        String[] features = getAngleEglFeatures(context, bundle);
-
-        if (setupAngleRulesApk(
-                anglePkgName, angleInfo, pm, packageName, paths, devOptIn, features)) {
-            // ANGLE with rules is set up from the APK, hence return.
-            return true;
-        }
-
-        return false;
+        return true;
     }
 
     /**
@@ -956,7 +846,7 @@
     private static native void setGpuStats(String driverPackageName, String driverVersionName,
             long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
     private static native void setAngleInfo(String path, String appPackage, String devOptIn,
-            String[] features, FileDescriptor rulesFd, long rulesOffset, long rulesLength);
+            String[] features);
     private static native boolean getShouldUseAngle(String packageName);
     private static native boolean setInjectLayersPrSetDumpable();
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ac520e8..a55484b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11845,6 +11845,12 @@
         public static final String WIFI_MIGRATION_COMPLETED = "wifi_migration_completed";
 
         /**
+         * Whether UWB should be enabled.
+         * @hide
+         */
+        public static final String UWB_ENABLED = "uwb_enabled";
+
+        /**
          * Value to specify whether network quality scores and badging should be shown in the UI.
          *
          * Type: int (0 for false, 1 for true)
@@ -13670,13 +13676,6 @@
                 "angle_gl_driver_selection_values";
 
         /**
-         * List of package names that should check ANGLE rules
-         * @hide
-         */
-        @Readable
-        public static final String ANGLE_ALLOWLIST = "angle_allowlist";
-
-        /**
          * Lists of ANGLE EGL features for debugging.
          * Each list of features is separated by a comma, each feature in each list is separated by
          * a colon.
@@ -14915,6 +14914,16 @@
                 "power_button_long_press";
 
         /**
+         * Override internal R.integer.config_longPressOnPowerDurationMs. It determines the length
+         * of power button press to be considered a long press in milliseconds.
+         * Used by PhoneWindowManager.
+         * @hide
+         */
+        @Readable
+        public static final String POWER_BUTTON_LONG_PRESS_DURATION_MS =
+                "power_button_long_press_duration_ms";
+
+        /**
          * Overrides internal R.integer.config_veryLongPressOnPowerBehavior.
          * Allowable values detailed in frameworks/base/core/res/res/values/config.xml.
          * Used by PhoneWindowManager.
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 8e4a68e..4004148 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -436,7 +436,7 @@
             try {
                 ApplicationInfo ai = context.getPackageManager()
                         .getApplicationInfoAsUser(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES,
-                                getUserId());
+                                getNormalizedUserId());
                 mContext = context.createApplicationContext(ai,
                         Context.CONTEXT_RESTRICTED);
             } catch (PackageManager.NameNotFoundException e) {
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index f477072..c198a49 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -96,6 +96,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
 
@@ -152,6 +153,7 @@
     private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
     private static final int MSG_ZOOM = 10100;
     private static final int MSG_SCALE_PREVIEW = 10110;
+    private static final int MSG_REPORT_SHOWN = 10150;
     private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
             Float.NEGATIVE_INFINITY);
 
@@ -527,6 +529,35 @@
         }
 
         /**
+         * This will be called in the end of {@link #updateSurface(boolean, boolean, boolean)}.
+         * If true is returned, the engine will not report shown until rendering finished is
+         * reported. Otherwise, the engine will report shown immediately right after redraw phase
+         * in {@link #updateSurface(boolean, boolean, boolean)}.
+         *
+         * @hide
+         */
+        public boolean shouldWaitForEngineShown() {
+            return false;
+        }
+
+        /**
+         * Reports the rendering is finished, stops waiting, then invokes
+         * {@link IWallpaperEngineWrapper#reportShown()}.
+         *
+         * @hide
+         */
+        public void reportEngineShown(boolean waitForEngineShown) {
+            if (mIWallpaperEngine.mShownReported) return;
+            Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN);
+            if (!waitForEngineShown) {
+                mCaller.removeMessages(MSG_REPORT_SHOWN);
+                mCaller.sendMessage(message);
+            } else {
+                mCaller.sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(1));
+            }
+        }
+
+        /**
          * Control whether this wallpaper will receive raw touch events
          * from the window manager as the user interacts with the window
          * that is currently displaying the wallpaper.  By default they
@@ -930,7 +961,7 @@
 
         void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
             if (mDestroyed) {
-                Log.w(TAG, "Ignoring updateSurface: destroyed");
+                Log.w(TAG, "Ignoring updateSurface due to destroyed");
             }
 
             boolean fixedSize = false;
@@ -1197,7 +1228,6 @@
                                         + this);
                             onVisibilityChanged(false);
                         }
-
                     } finally {
                         mIsCreating = false;
                         mSurfaceCreated = true;
@@ -1207,7 +1237,7 @@
                             processLocalColors(mPendingXOffset, mPendingXOffsetStep);
                         }
                         reposition();
-                        mIWallpaperEngine.reportShown();
+                        reportEngineShown(shouldWaitForEngineShown());
                     }
                 } catch (RemoteException ex) {
                 }
@@ -2201,6 +2231,9 @@
                         // Connection went away, nothing to do in here.
                     }
                 } break;
+                case MSG_REPORT_SHOWN: {
+                    reportShown();
+                } break;
                 default :
                     Log.w(TAG, "Unknown message type " + message.what);
             }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 8021636..a7ecf1f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -814,9 +814,10 @@
      * @param displayId The display associated with the window context
      * @param options A bundle used to pass window-related options and choose the right DisplayArea
      *
-     * @return {@code true} if the WindowContext is attached to the DisplayArea successfully.
+     * @return the DisplayArea's {@link android.app.res.Configuration} if the WindowContext is
+     * attached to the DisplayArea successfully. {@code null}, otherwise.
      */
-    boolean attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
+    Configuration attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
             in Bundle options);
 
     /**
diff --git a/core/java/android/view/ScrollCaptureResponse.java b/core/java/android/view/ScrollCaptureResponse.java
index 8808827..758f9ab 100644
--- a/core/java/android/view/ScrollCaptureResponse.java
+++ b/core/java/android/view/ScrollCaptureResponse.java
@@ -53,6 +53,10 @@
     @Nullable
     private String mWindowTitle = null;
 
+    /** The package which owns the window. */
+    @Nullable
+    private String mPackageName = null;
+
     /** Carries additional logging and debugging information when enabled. */
     @NonNull
     @DataClass.PluralOf("message")
@@ -77,7 +81,7 @@
 
 
 
-    // Code below generated by codegen v1.0.22.
+    // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -97,6 +101,7 @@
             @Nullable Rect windowBounds,
             @Nullable Rect boundsInWindow,
             @Nullable String windowTitle,
+            @Nullable String packageName,
             @NonNull ArrayList<String> messages) {
         this.mDescription = description;
         com.android.internal.util.AnnotationValidations.validate(
@@ -105,6 +110,7 @@
         this.mWindowBounds = windowBounds;
         this.mBoundsInWindow = boundsInWindow;
         this.mWindowTitle = windowTitle;
+        this.mPackageName = packageName;
         this.mMessages = messages;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mMessages);
@@ -153,6 +159,14 @@
     }
 
     /**
+     * The package name of the process the window is owned by.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
      * Carries additional logging and debugging information when enabled.
      */
     @DataClass.Generated.Member
@@ -172,6 +186,7 @@
                 "windowBounds = " + mWindowBounds + ", " +
                 "boundsInWindow = " + mBoundsInWindow + ", " +
                 "windowTitle = " + mWindowTitle + ", " +
+                "packageName = " + mPackageName + ", " +
                 "messages = " + mMessages +
         " }";
     }
@@ -187,12 +202,14 @@
         if (mWindowBounds != null) flg |= 0x4;
         if (mBoundsInWindow != null) flg |= 0x8;
         if (mWindowTitle != null) flg |= 0x10;
+        if (mPackageName != null) flg |= 0x20;
         dest.writeByte(flg);
         dest.writeString(mDescription);
         if (mConnection != null) dest.writeStrongInterface(mConnection);
         if (mWindowBounds != null) dest.writeTypedObject(mWindowBounds, flags);
         if (mBoundsInWindow != null) dest.writeTypedObject(mBoundsInWindow, flags);
         if (mWindowTitle != null) dest.writeString(mWindowTitle);
+        if (mPackageName != null) dest.writeString(mPackageName);
         dest.writeStringList(mMessages);
     }
 
@@ -213,6 +230,7 @@
         Rect windowBounds = (flg & 0x4) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR);
         Rect boundsInWindow = (flg & 0x8) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR);
         String windowTitle = (flg & 0x10) == 0 ? null : in.readString();
+        String packageName = (flg & 0x20) == 0 ? null : in.readString();
         ArrayList<String> messages = new ArrayList<>();
         in.readStringList(messages);
 
@@ -223,6 +241,7 @@
         this.mWindowBounds = windowBounds;
         this.mBoundsInWindow = boundsInWindow;
         this.mWindowTitle = windowTitle;
+        this.mPackageName = packageName;
         this.mMessages = messages;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mMessages);
@@ -256,6 +275,7 @@
         private @Nullable Rect mWindowBounds;
         private @Nullable Rect mBoundsInWindow;
         private @Nullable String mWindowTitle;
+        private @Nullable String mPackageName;
         private @NonNull ArrayList<String> mMessages;
 
         private long mBuilderFieldsSet = 0L;
@@ -319,12 +339,23 @@
         }
 
         /**
+         * The package name of the process the window is owned by.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setPackageName(@NonNull String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x20;
+            mPackageName = value;
+            return this;
+        }
+
+        /**
          * Carries additional logging and debugging information when enabled.
          */
         @DataClass.Generated.Member
         public @NonNull Builder setMessages(@NonNull ArrayList<String> value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x20;
+            mBuilderFieldsSet |= 0x40;
             mMessages = value;
             return this;
         }
@@ -340,7 +371,7 @@
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull ScrollCaptureResponse build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x40; // Mark builder used
+            mBuilderFieldsSet |= 0x80; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mDescription = "";
@@ -358,6 +389,9 @@
                 mWindowTitle = null;
             }
             if ((mBuilderFieldsSet & 0x20) == 0) {
+                mPackageName = null;
+            }
+            if ((mBuilderFieldsSet & 0x40) == 0) {
                 mMessages = new ArrayList<>();
             }
             ScrollCaptureResponse o = new ScrollCaptureResponse(
@@ -366,12 +400,13 @@
                     mWindowBounds,
                     mBoundsInWindow,
                     mWindowTitle,
+                    mPackageName,
                     mMessages);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x40) != 0) {
+            if ((mBuilderFieldsSet & 0x80) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -379,10 +414,10 @@
     }
 
     @DataClass.Generated(
-            time = 1614833185795L,
-            codegenVersion = "1.0.22",
+            time = 1628630366187L,
+            codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/ScrollCaptureResponse.java",
-            inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList<java.lang.String> mMessages\npublic  boolean isConnected()\npublic  void close()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)")
+            inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList<java.lang.String> mMessages\npublic  boolean isConnected()\npublic  void close()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3550a31..985f20b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -9493,6 +9493,7 @@
 
         ScrollCaptureResponse.Builder response = new ScrollCaptureResponse.Builder();
         response.setWindowTitle(getTitle().toString());
+        response.setPackageName(mContext.getPackageName());
 
         StringWriter writer =  new StringWriter();
         IndentingPrintWriter pw = new IndentingPrintWriter(writer);
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 91fc5a5..fe5eb08 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -740,6 +740,16 @@
         }
 
         @Override
+        public UserHandle getUser() {
+            return mContextForResources.getUser();
+        }
+
+        @Override
+        public int getUserId() {
+            return mContextForResources.getUserId();
+        }
+
+        @Override
         public boolean isRestricted() {
             // Override isRestricted and direct to resource's implementation. The isRestricted is
             // used for determining the risky resources loading, e.g. fonts, thus direct to context
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index 8b8dba8..69bc1b5 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -88,4 +88,9 @@
      * user has pressed back on the root activity of a task controlled by the task organizer.
      */
     void onBackPressedOnTaskRoot(in ActivityManager.RunningTaskInfo taskInfo);
+
+    /**
+     * Called when the IME has drawn on the organized task.
+     */
+    void onImeDrawnOnTask(int taskId);
 }
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index c7c91cd..d8723a8 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -144,6 +144,10 @@
     @BinderThread
     public void onBackPressedOnTaskRoot(@NonNull ActivityManager.RunningTaskInfo taskInfo) {}
 
+    /** @hide */
+    @BinderThread
+    public void onImeDrawnOnTask(int taskId) {}
+
     /**
      * Creates a persistent root task in WM for a particular windowing-mode.
      * @param displayId The display to create the root task on.
@@ -287,6 +291,11 @@
         public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo info) {
             mExecutor.execute(() -> TaskOrganizer.this.onBackPressedOnTaskRoot(info));
         }
+
+        @Override
+        public void onImeDrawnOnTask(int taskId) {
+            mExecutor.execute(() -> TaskOrganizer.this.onImeDrawnOnTask(taskId));
+        }
     };
 
     private ITaskOrganizerController getController() {
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index 901625b..6d0a6bd 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -26,7 +26,6 @@
 import android.content.ContextWrapper;
 import android.content.res.Configuration;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -67,7 +66,7 @@
         mType = type;
         mOptions = options;
         mWindowManager = createWindowContextWindowManager(this);
-        IBinder token = getWindowContextToken();
+        WindowTokenClient token = (WindowTokenClient) getWindowContextToken();
         mController = new WindowContextController(token);
 
         Reference.reachabilityFence(this);
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index d84f571..505b450 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -46,7 +47,7 @@
     @VisibleForTesting
     public boolean mAttachedToDisplayArea;
     @NonNull
-    private final IBinder mToken;
+    private final WindowTokenClient mToken;
 
     /**
      * Window Context Controller constructor
@@ -54,14 +55,13 @@
      * @param token The token used to attach to a window manager node. It is usually from
      *              {@link Context#getWindowContextToken()}.
      */
-    public WindowContextController(@NonNull IBinder token) {
-        mToken = token;
-        mWms = WindowManagerGlobal.getWindowManagerService();
+    public WindowContextController(@NonNull WindowTokenClient token) {
+        this(token, WindowManagerGlobal.getWindowManagerService());
     }
 
     /** Used for test only. DO NOT USE it in production code. */
     @VisibleForTesting
-    public WindowContextController(@NonNull IBinder token, IWindowManager mockWms) {
+    public WindowContextController(@NonNull WindowTokenClient token, IWindowManager mockWms) {
         mToken = token;
         mWms = mockWms;
     }
@@ -81,8 +81,14 @@
                     + "a DisplayArea once.");
         }
         try {
-            mAttachedToDisplayArea = mWms.attachWindowContextToDisplayArea(mToken, type, displayId,
-                    options);
+            final Configuration configuration = mWms.attachWindowContextToDisplayArea(mToken, type,
+                    displayId, options);
+            if (configuration != null) {
+                mAttachedToDisplayArea = true;
+                // Send the DisplayArea's configuration to WindowContext directly instead of
+                // waiting for dispatching from WMS.
+                mToken.onConfigurationChanged(configuration, displayId);
+            }
         }  catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 6abf557..4dcd2e7 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -24,6 +24,8 @@
 import android.os.Bundle;
 import android.os.IBinder;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.ref.WeakReference;
 
 /**
@@ -33,7 +35,7 @@
  * {@link Context#getWindowContextToken() the token of non-Activity UI Contexts}.
  *
  * @see WindowContext
- * @see android.view.IWindowManager#registerWindowContextListener(IBinder, int, int, Bundle)
+ * @see android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int, Bundle)
  *
  * @hide
  */
@@ -50,8 +52,8 @@
      * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
      * can only attach one {@link Context}.
      * <p>This method must be called before invoking
-     * {@link android.view.IWindowManager#registerWindowContextListener(IBinder, int, int,
-     * Bundle, boolean)}.<p/>
+     * {@link android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int,
+     * Bundle)}.<p/>
      *
      * @param context context to be attached
      * @throws IllegalStateException if attached context has already existed.
@@ -63,6 +65,13 @@
         mContextRef = new WeakReference<>(context);
     }
 
+    /**
+     * Called when {@link Configuration} updates from the server side receive.
+     *
+     * @param newConfig the updated {@link Configuration}
+     * @param newDisplayId the updated {@link android.view.Display} ID
+     */
+    @VisibleForTesting
     @Override
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
         final Context context = mContextRef.get();
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index ce75f45..068b882 100644
--- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -165,7 +165,7 @@
 
         // Now fetch app icon and raster with no badging even in work profile
         Bitmap appIcon = mSelectableTargetInfoCommunicator.makePresentationGetter(info)
-                .getIconBitmap(android.os.Process.myUserHandle());
+                .getIconBitmap(mContext.getUser());
 
         // Raster target drawable with appIcon as a badge
         SimpleIconFactory sif = SimpleIconFactory.obtain(mContext);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 8c63f38..7e286f0 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -707,6 +707,10 @@
      * Mapping isolated uids to the actual owning app uid.
      */
     final SparseIntArray mIsolatedUids = new SparseIntArray();
+    /**
+     * Internal reference count of isolated uids.
+     */
+    final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
 
     /**
      * The statistics we have collected organized by uids.
@@ -3897,6 +3901,7 @@
     public void addIsolatedUidLocked(int isolatedUid, int appUid,
             long elapsedRealtimeMs, long uptimeMs) {
         mIsolatedUids.put(isolatedUid, appUid);
+        mIsolatedUidRefCounts.put(isolatedUid, 1);
         final Uid u = getUidStatsLocked(appUid, elapsedRealtimeMs, uptimeMs);
         u.addIsolatedUid(isolatedUid);
     }
@@ -3915,19 +3920,51 @@
     }
 
     /**
-     * This should only be called after the cpu times have been read.
+     * Isolated uid should only be removed after all wakelocks associated with the uid are stopped
+     * and the cpu time-in-state has been read one last time for the uid.
+     *
      * @see #scheduleRemoveIsolatedUidLocked(int, int)
+     *
+     * @return true if the isolated uid is actually removed.
      */
     @GuardedBy("this")
-    public void removeIsolatedUidLocked(int isolatedUid, long elapsedRealtimeMs, long uptimeMs) {
+    public boolean maybeRemoveIsolatedUidLocked(int isolatedUid, long elapsedRealtimeMs,
+            long uptimeMs) {
+        final int refCount = mIsolatedUidRefCounts.get(isolatedUid, 0) - 1;
+        if (refCount > 0) {
+            // Isolated uid is still being tracked
+            mIsolatedUidRefCounts.put(isolatedUid, refCount);
+            return false;
+        }
+
         final int idx = mIsolatedUids.indexOfKey(isolatedUid);
         if (idx >= 0) {
             final int ownerUid = mIsolatedUids.valueAt(idx);
             final Uid u = getUidStatsLocked(ownerUid, elapsedRealtimeMs, uptimeMs);
             u.removeIsolatedUid(isolatedUid);
             mIsolatedUids.removeAt(idx);
+            mIsolatedUidRefCounts.delete(isolatedUid);
+        } else {
+            Slog.w(TAG, "Attempted to remove untracked isolated uid (" + isolatedUid + ")");
         }
         mPendingRemovedUids.add(new UidToRemove(isolatedUid, elapsedRealtimeMs));
+
+        return true;
+    }
+
+    /**
+     * Increment the ref count for an isolated uid.
+     * call #maybeRemoveIsolatedUidLocked to decrement.
+     */
+    public void incrementIsolatedUidRefCount(int uid) {
+        final int refCount = mIsolatedUidRefCounts.get(uid, 0);
+        if (refCount <= 0) {
+            // Uid is not mapped or referenced
+            Slog.w(TAG,
+                    "Attempted to increment ref counted of untracked isolated uid (" + uid + ")");
+            return;
+        }
+        mIsolatedUidRefCounts.put(uid, refCount + 1);
     }
 
     public int mapUid(int uid) {
@@ -4287,7 +4324,7 @@
 
     public void noteStartWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
             int type, boolean unimportantForLogging, long elapsedRealtimeMs, long uptimeMs) {
-        uid = mapUid(uid);
+        final int mappedUid = mapUid(uid);
         if (type == WAKE_TYPE_PARTIAL) {
             // Only care about partial wake locks, since full wake locks
             // will be canceled when the user puts the screen to sleep.
@@ -4297,9 +4334,9 @@
             }
             if (mRecordAllHistory) {
                 if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName,
-                        uid, 0)) {
+                        mappedUid, 0)) {
                     addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
-                            HistoryItem.EVENT_WAKE_LOCK_START, historyName, uid);
+                            HistoryItem.EVENT_WAKE_LOCK_START, historyName, mappedUid);
                 }
             }
             if (mWakeLockNesting == 0) {
@@ -4308,7 +4345,7 @@
                         + Integer.toHexString(mHistoryCur.states));
                 mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
                 mHistoryCur.wakelockTag.string = mInitialAcquireWakeName = historyName;
-                mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid;
+                mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = mappedUid;
                 mWakeLockImportant = !unimportantForLogging;
                 addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             } else if (!mWakeLockImportant && !unimportantForLogging
@@ -4318,14 +4355,19 @@
                     mHistoryLastWritten.wakelockTag = null;
                     mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
                     mHistoryCur.wakelockTag.string = mInitialAcquireWakeName = historyName;
-                    mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid;
+                    mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = mappedUid;
                     addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
                 }
                 mWakeLockImportant = true;
             }
             mWakeLockNesting++;
         }
-        if (uid >= 0) {
+        if (mappedUid >= 0) {
+            if (mappedUid != uid) {
+                // Prevent the isolated uid mapping from being removed while the wakelock is
+                // being held.
+                incrementIsolatedUidRefCount(uid);
+            }
             if (mOnBatteryScreenOffTimeBase.isRunning()) {
                 // We only update the cpu time when a wake lock is acquired if the screen is off.
                 // If the screen is on, we don't distribute the power amongst partial wakelocks.
@@ -4335,7 +4377,7 @@
                 requestWakelockCpuUpdate();
             }
 
-            getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
+            getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs)
                     .noteStartWakeLocked(pid, name, type, elapsedRealtimeMs);
 
             if (wc != null) {
@@ -4343,8 +4385,8 @@
                         wc.getTags(), getPowerManagerWakeLockLevel(type), name,
                         FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE);
             } else {
-                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, uid,
-                        null, getPowerManagerWakeLockLevel(type), name,
+                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
+                        mappedUid, null, getPowerManagerWakeLockLevel(type), name,
                         FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE);
             }
         }
@@ -4358,7 +4400,7 @@
 
     public void noteStopWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
             int type, long elapsedRealtimeMs, long uptimeMs) {
-        uid = mapUid(uid);
+        final int mappedUid = mapUid(uid);
         if (type == WAKE_TYPE_PARTIAL) {
             mWakeLockNesting--;
             if (mRecordAllHistory) {
@@ -4366,9 +4408,9 @@
                     historyName = name;
                 }
                 if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName,
-                        uid, 0)) {
+                        mappedUid, 0)) {
                     addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
-                            HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, uid);
+                            HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, mappedUid);
                 }
             }
             if (mWakeLockNesting == 0) {
@@ -4380,7 +4422,7 @@
                 addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             }
         }
-        if (uid >= 0) {
+        if (mappedUid >= 0) {
             if (mOnBatteryScreenOffTimeBase.isRunning()) {
                 if (DEBUG_ENERGY_CPU) {
                     Slog.d(TAG, "Updating cpu time because of -wake_lock");
@@ -4388,17 +4430,22 @@
                 requestWakelockCpuUpdate();
             }
 
-            getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
+            getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs)
                     .noteStopWakeLocked(pid, name, type, elapsedRealtimeMs);
             if (wc != null) {
                 FrameworkStatsLog.write(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(),
                         wc.getTags(), getPowerManagerWakeLockLevel(type), name,
                         FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE);
             } else {
-                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, uid,
-                        null, getPowerManagerWakeLockLevel(type), name,
+                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
+                        mappedUid, null, getPowerManagerWakeLockLevel(type), name,
                         FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE);
             }
+
+            if (mappedUid != uid) {
+                // Decrement the ref count for the isolated uid and delete the mapping if uneeded.
+                maybeRemoveIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs);
+            }
         }
     }
 
@@ -16761,6 +16808,15 @@
         pw.print("UIDs removed since the later of device start or stats reset: ");
         pw.println(mNumUidsRemoved);
 
+        pw.println("Currently mapped isolated uids:");
+        final int numIsolatedUids = mIsolatedUids.size();
+        for (int i = 0; i < numIsolatedUids; i++) {
+            final int isolatedUid = mIsolatedUids.keyAt(i);
+            final int ownerUid = mIsolatedUids.valueAt(i);
+            final int refCount = mIsolatedUidRefCounts.get(isolatedUid);
+            pw.println("  " + isolatedUid + "->" + ownerUid + " (ref count = " + refCount + ")");
+        }
+
         pw.println();
         dumpConstantsLocked(pw);
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 10f14b4..ed6415d 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -148,7 +148,7 @@
     */
     void showAuthenticationDialog(in PromptInfo promptInfo, IBiometricSysuiReceiver sysuiReceiver,
             in int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, int userId,
-            String opPackageName, long operationId, int multiSensorConfig);
+            long operationId, String opPackageName, long requestId, int multiSensorConfig);
     /**
     * Used to notify the authentication dialog that a biometric has been authenticated.
     */
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index e7d6d6c..b3499db 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -110,7 +110,8 @@
     // Used to show the authentication dialog (Biometrics, Device Credential)
     void showAuthenticationDialog(in PromptInfo promptInfo, IBiometricSysuiReceiver sysuiReceiver,
             in int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
-            int userId, String opPackageName, long operationId, int multiSensorConfig);
+            int userId, long operationId, String opPackageName, long requestId,
+            int multiSensorConfig);
 
     // Used to notify the authentication dialog that a biometric has been authenticated
     void onBiometricAuthenticated();
diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java
index 8b3c133..7a712e5 100644
--- a/core/java/com/android/internal/util/ContrastColorUtil.java
+++ b/core/java/com/android/internal/util/ContrastColorUtil.java
@@ -291,10 +291,10 @@
      * Finds a suitable color such that there's enough contrast.
      *
      * @param color the color to start searching from.
-     * @param other the color to ensure contrast against. Assumed to be lighter than {@param color}
-     * @param findFg if true, we assume {@param color} is a foreground, otherwise a background.
+     * @param other the color to ensure contrast against. Assumed to be lighter than {@code color}
+     * @param findFg if true, we assume {@code color} is a foreground, otherwise a background.
      * @param minRatio the minimum contrast ratio required.
-     * @return a color with the same hue as {@param color}, potentially darkened to meet the
+     * @return a color with the same hue as {@code color}, potentially darkened to meet the
      *          contrast ratio.
      */
     public static int findContrastColor(int color, int other, boolean findFg, double minRatio) {
@@ -331,7 +331,7 @@
      * @param color the color to start searching from.
      * @param backgroundColor the color to ensure contrast against.
      * @param minRatio the minimum contrast ratio required.
-     * @return the same color as {@param color} with potentially modified alpha to meet contrast
+     * @return the same color as {@code color} with potentially modified alpha to meet contrast
      */
     public static int findAlphaToMeetContrast(int color, int backgroundColor, double minRatio) {
         int fg = color;
@@ -361,10 +361,10 @@
      * Finds a suitable color such that there's enough contrast.
      *
      * @param color the color to start searching from.
-     * @param other the color to ensure contrast against. Assumed to be darker than {@param color}
-     * @param findFg if true, we assume {@param color} is a foreground, otherwise a background.
+     * @param other the color to ensure contrast against. Assumed to be darker than {@code color}
+     * @param findFg if true, we assume {@code color} is a foreground, otherwise a background.
      * @param minRatio the minimum contrast ratio required.
-     * @return a color with the same hue as {@param color}, potentially darkened to meet the
+     * @return a color with the same hue as {@code color}, potentially lightened to meet the
      *          contrast ratio.
      */
     public static int findContrastColorAgainstDark(int color, int other, boolean findFg,
@@ -393,7 +393,8 @@
                 low = l;
             }
         }
-        return findFg ? fg : bg;
+        hsl[2] = high;
+        return ColorUtilsFromCompat.HSLToColor(hsl);
     }
 
     public static int ensureTextContrastOnBlack(int color) {
@@ -452,7 +453,7 @@
     }
 
     /**
-     * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
+     * Resolves {@code color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
      */
     public static int resolveColor(Context context, int color, boolean defaultBackgroundIsDark) {
         if (color == Notification.COLOR_DEFAULT) {
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 498505c..cd1bbb6 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -17,6 +17,7 @@
 package com.android.internal.widget;
 
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
@@ -1272,6 +1273,14 @@
     }
 
     /**
+     * Whether the user is not allowed to set any credentials via PASSWORD_QUALITY_MANAGED.
+     */
+    public boolean isCredentialsDisabledForUser(int userId) {
+        return getDevicePolicyManager().getPasswordQuality(/* admin= */ null, userId)
+                == PASSWORD_QUALITY_MANAGED;
+    }
+
+    /**
      * @see StrongAuthTracker#isTrustAllowedForUser
      */
     public boolean isTrustAllowedForUser(int userId) {
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index 0c2d2a9..3191e23 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -53,6 +53,8 @@
     private int mEmphasizedHeight;
     private int mRegularHeight;
     @DimenRes private int mCollapsibleIndentDimen = R.dimen.notification_actions_padding_start;
+    int mNumNotGoneChildren;
+    int mNumPriorityChildren;
 
     public NotificationActionListLayout(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -76,15 +78,14 @@
                 && ((EmphasizedNotificationButton) actionView).isPriority();
     }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        final int N = getChildCount();
+    private void countAndRebuildMeasureOrder() {
+        final int numChildren = getChildCount();
         int textViews = 0;
         int otherViews = 0;
-        int notGoneChildren = 0;
-        int priorityChildren = 0;
+        mNumNotGoneChildren = 0;
+        mNumPriorityChildren = 0;
 
-        for (int i = 0; i < N; i++) {
+        for (int i = 0; i < numChildren; i++) {
             View c = getChildAt(i);
             if (c instanceof TextView) {
                 textViews++;
@@ -92,9 +93,9 @@
                 otherViews++;
             }
             if (c.getVisibility() != GONE) {
-                notGoneChildren++;
+                mNumNotGoneChildren++;
                 if (isPriority(c)) {
-                    priorityChildren++;
+                    mNumPriorityChildren++;
                 }
             }
         }
@@ -119,17 +120,20 @@
         if (needRebuild) {
             rebuildMeasureOrder(textViews, otherViews);
         }
+    }
 
+    private int measureAndGetUsedWidth(int widthMeasureSpec, int heightMeasureSpec, int innerWidth,
+            boolean collapsePriorityActions) {
+        final int numChildren = getChildCount();
         final boolean constrained =
                 MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED;
-
-        final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
         final int otherSize = mMeasureOrderOther.size();
         int usedWidth = 0;
 
+        int maxPriorityWidth = 0;
         int measuredChildren = 0;
         int measuredPriorityChildren = 0;
-        for (int i = 0; i < N; i++) {
+        for (int i = 0; i < numChildren; i++) {
             // Measure shortest children first. To avoid measuring twice, we approximate by looking
             // at the text length.
             final boolean isPriority;
@@ -154,12 +158,20 @@
                 // measure in the order of (approx.) size, a large view can still take more than its
                 // share if the others are small.
                 int availableWidth = innerWidth - usedWidth;
-                int unmeasuredChildren = notGoneChildren - measuredChildren;
+                int unmeasuredChildren = mNumNotGoneChildren - measuredChildren;
                 int maxWidthForChild = availableWidth / unmeasuredChildren;
-                if (isPriority) {
+                if (isPriority && collapsePriorityActions) {
+                    // Collapsing the actions to just the width required to show the icon.
+                    if (maxPriorityWidth == 0) {
+                        maxPriorityWidth = getResources().getDimensionPixelSize(
+                                R.dimen.notification_actions_collapsed_priority_width);
+                    }
+                    maxWidthForChild = maxPriorityWidth + lp.leftMargin + lp.rightMargin;
+                } else if (isPriority) {
                     // Priority children get a larger maximum share of the total space:
                     //  maximum priority share = (nPriority + 1) / (MAX + 1)
-                    int unmeasuredPriorityChildren = priorityChildren - measuredPriorityChildren;
+                    int unmeasuredPriorityChildren = mNumPriorityChildren
+                            - measuredPriorityChildren;
                     int unmeasuredOtherChildren = unmeasuredChildren - unmeasuredPriorityChildren;
                     int widthReservedForOtherChildren = innerWidth * unmeasuredOtherChildren
                             / (Notification.MAX_ACTION_BUTTONS + 1);
@@ -187,6 +199,19 @@
         } else {
             mExtraStartPadding = 0;
         }
+        return usedWidth;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        countAndRebuildMeasureOrder();
+        final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
+        int usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
+                false /* collapsePriorityButtons */);
+        if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+            usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
+                    true /* collapsePriorityButtons */);
+        }
 
         mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
         setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index b40491a..f44e829 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -50,8 +50,7 @@
 }
 
 void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName,
-                         jstring devOptIn, jobjectArray featuresObj, jobject rulesFd,
-                         jlong rulesOffset, jlong rulesLength) {
+                         jstring devOptIn, jobjectArray featuresObj) {
     ScopedUtfChars pathChars(env, path);
     ScopedUtfChars appNameChars(env, appName);
     ScopedUtfChars devOptInChars(env, devOptIn);
@@ -74,11 +73,8 @@
         }
     }
 
-    int rulesFd_native = jniGetFDFromFileDescriptor(env, rulesFd);
-
     android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), appNameChars.c_str(),
-                                                     devOptInChars.c_str(), features,
-                                                     rulesFd_native, rulesOffset, rulesLength);
+                                                     devOptInChars.c_str(), features);
 }
 
 bool shouldUseAngle_native(JNIEnv* env, jobject clazz, jstring appName) {
@@ -124,8 +120,7 @@
         {"setInjectLayersPrSetDumpable", "()Z",
          reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
         {"setAngleInfo",
-         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/io/"
-         "FileDescriptor;JJ)V",
+         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V",
          reinterpret_cast<void*>(setAngleInfo_native)},
         {"getShouldUseAngle", "(Ljava/lang/String;)Z",
          reinterpret_cast<void*>(shouldUseAngle_native)},
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index c3d1596..83e26f6 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -771,6 +771,8 @@
     optional SettingProto power_manager_constants = 93;
     reserved 94; // Used to be priv_app_oob_enabled
 
+    optional SettingProto power_button_long_press_duration_ms = 154 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
     message PrepaidSetup {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
 
@@ -970,6 +972,7 @@
     optional SettingProto usb_mass_storage_enabled = 127 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto use_google_mail = 128 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto use_open_wifi_package = 129 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    optional SettingProto uwb_enabled = 155 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto vt_ims_enabled = 130 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto wait_for_debugger = 131 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
@@ -1063,5 +1066,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 154;
+    // Next tag = 156;
 }
diff --git a/core/res/res/drawable/btn_notification_emphasized.xml b/core/res/res/drawable/btn_notification_emphasized.xml
index 29c51f2a..7c09fb8 100644
--- a/core/res/res/drawable/btn_notification_emphasized.xml
+++ b/core/res/res/drawable/btn_notification_emphasized.xml
@@ -24,9 +24,9 @@
             android:insetBottom="@dimen/button_inset_vertical_material">
             <shape android:shape="rectangle">
                 <corners android:radius="@dimen/notification_action_button_radius" />
-                <padding android:left="12dp"
+                <padding android:left="16dp"
                          android:top="@dimen/button_padding_vertical_material"
-                         android:right="12dp"
+                         android:right="16dp"
                          android:bottom="@dimen/button_padding_vertical_material" />
                 <solid android:color="@color/white" />
             </shape>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index db43b5b..e1e1201 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -957,6 +957,20 @@
     -->
     <integer name="config_longPressOnPowerBehavior">5</integer>
 
+    <!-- The time in milliseconds after which a press on power button is considered "long". -->
+    <integer name="config_longPressOnPowerDurationMs">500</integer>
+
+    <!-- The possible UI options to be surfaced for configuring long press power on duration
+         action. Value set in config_longPressOnPowerDurationMs should be one of the available
+         options to allow users to restore default. -->
+    <integer-array name="config_longPressOnPowerDurationSettings">
+        <item>250</item>
+        <item>350</item>
+        <item>500</item>
+        <item>650</item>
+        <item>750</item>
+    </integer-array>
+
     <!-- Whether the setting to change long press on power behaviour from default to assistant (5)
          is available in Settings.
      -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index de7a117..2baed43 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -237,6 +237,9 @@
          value is calculated in ConversationLayout#updateActionListPadding() -->
     <dimen name="notification_actions_padding_start">36dp</dimen>
 
+    <!-- The max width of a priority action button when it is collapsed to just the icon. -->
+    <dimen name="notification_actions_collapsed_priority_width">60dp</dimen>
+
     <!-- The start padding to optionally use (e.g. if there's extra space) for CallStyle
          notification actions.
          this = conversation_content_start (80dp) - button inset (4dp) - action padding (12dp) -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a99a220..b58638c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1650,6 +1650,8 @@
     <!-- Array containing custom error messages from vendor.  Vendor is expected to add and translate these strings -->
     <string-array name="fingerprint_error_vendor">
     </string-array>
+    <!-- Default error message to use when fingerprint_error_vendor does not contain a message. [CHAR LIMIT=NONE] -->
+    <string name="fingerprint_error_vendor_unknown">Something went wrong. Try again.</string>
 
     <!-- Content description which should be used for the fingerprint icon. -->
     <string name="fingerprint_icon_content_description">Fingerprint icon</string>
@@ -1760,6 +1762,8 @@
     <!-- Array containing custom error messages from vendor.  Vendor is expected to add and translate these strings -->
     <string-array name="face_error_vendor">
     </string-array>
+    <!-- Default error message to use when face_error_vendor does not contain a message. [CHAR LIMIT=NONE] -->
+    <string name="face_error_vendor_unknown">Something went wrong. Try again.</string>
 
     <!-- Content description which should be used for the face icon. [CHAR LIMIT=10] -->
     <string name="face_icon_content_description">Face icon</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index adb046e..7799b8e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -439,6 +439,8 @@
   <java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
   <java-symbol type="integer" name="config_immersive_mode_confirmation_panic" />
   <java-symbol type="integer" name="config_longPressOnPowerBehavior" />
+  <java-symbol type="integer" name="config_longPressOnPowerDurationMs" />
+  <java-symbol type="array" name="config_longPressOnPowerDurationSettings" />
   <java-symbol type="bool" name="config_longPressOnPowerForAssistantSettingAvailable" />
   <java-symbol type="integer" name="config_veryLongPressOnPowerBehavior" />
   <java-symbol type="integer" name="config_veryLongPressTimeout" />
@@ -2523,6 +2525,7 @@
   <java-symbol type="string" name="fingerprint_error_no_space" />
   <java-symbol type="string" name="fingerprint_error_timeout" />
   <java-symbol type="array" name="fingerprint_error_vendor" />
+  <java-symbol type="string" name="fingerprint_error_vendor_unknown" />
   <java-symbol type="string" name="fingerprint_acquired_partial" />
   <java-symbol type="string" name="fingerprint_acquired_insufficient" />
   <java-symbol type="string" name="fingerprint_acquired_imager_dirty" />
@@ -2562,6 +2565,7 @@
   <java-symbol type="string" name="face_error_no_space" />
   <java-symbol type="string" name="face_error_timeout" />
   <java-symbol type="array" name="face_error_vendor" />
+  <java-symbol type="string" name="face_error_vendor_unknown" />
   <java-symbol type="string" name="face_error_canceled" />
   <java-symbol type="string" name="face_error_user_canceled" />
   <java-symbol type="string" name="face_error_lockout" />
@@ -3183,6 +3187,7 @@
 
   <java-symbol type="id" name="notification_action_list_margin_target" />
   <java-symbol type="dimen" name="notification_actions_padding_start"/>
+  <java-symbol type="dimen" name="notification_actions_collapsed_priority_width"/>
   <java-symbol type="dimen" name="notification_action_disabled_alpha" />
   <java-symbol type="id" name="tag_margin_end_when_icon_visible" />
   <java-symbol type="id" name="tag_margin_end_when_icon_gone" />
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index cd07d46..34c1763 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -16,9 +16,11 @@
 
 package android.app;
 
-import static androidx.core.graphics.ColorUtils.calculateContrast;
+import static android.app.Notification.Builder.ensureColorSpanContrast;
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.internal.util.ContrastColorUtilTest.assertContrastIsAtLeast;
+import static com.android.internal.util.ContrastColorUtilTest.assertContrastIsWithinRange;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -35,6 +37,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.LocusId;
+import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
@@ -42,12 +45,21 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.TextAppearanceSpan;
 import android.widget.RemoteViews;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.R;
+import com.android.internal.util.ContrastColorUtil;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -334,6 +346,163 @@
     }
 
     @Test
+    public void testBuilder_getFullLengthSpanColor_returnsNullForString() {
+        assertThat(Notification.Builder.getFullLengthSpanColor("String")).isNull();
+    }
+
+    @Test
+    public void testBuilder_getFullLengthSpanColor_returnsNullWithPartialSpan() {
+        CharSequence text = new SpannableStringBuilder()
+                .append("text with ")
+                .append("some red", new ForegroundColorSpan(Color.RED),
+                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        assertThat(Notification.Builder.getFullLengthSpanColor(text)).isNull();
+    }
+
+    @Test
+    public void testBuilder_getFullLengthSpanColor_worksWithSingleSpan() {
+        CharSequence text = new SpannableStringBuilder()
+                .append("text that is all red", new ForegroundColorSpan(Color.RED),
+                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(Color.RED);
+    }
+
+    @Test
+    public void testBuilder_getFullLengthSpanColor_worksWithFullAndPartialSpans() {
+        Spannable text = new SpannableString("blue text with yellow and green");
+        text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(new ForegroundColorSpan(Color.BLUE), 0, text.length(),
+                Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(Color.BLUE);
+    }
+
+    @Test
+    public void testBuilder_getFullLengthSpanColor_worksWithTextAppearance() {
+        Spannable text = new SpannableString("title text with yellow and green");
+        text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(mContext,
+                R.style.TextAppearance_DeviceDefault_Notification_Title);
+        int expectedTextColor = textAppearanceSpan.getTextColor().getDefaultColor();
+        text.setSpan(textAppearanceSpan, 0, text.length(),
+                Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(expectedTextColor);
+    }
+
+    @Test
+    public void testBuilder_ensureColorSpanContrast_removesAllFullLengthColorSpans() {
+        Spannable text = new SpannableString("blue text with yellow and green");
+        text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(new ForegroundColorSpan(Color.BLUE), 0, text.length(),
+                Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        TextAppearanceSpan taSpan = new TextAppearanceSpan(mContext,
+                R.style.TextAppearance_DeviceDefault_Notification_Title);
+        assertThat(taSpan.getTextColor()).isNotNull();  // it must be set to prove it is cleared.
+        text.setSpan(taSpan, 0, text.length(),
+                Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        Spannable result = (Spannable) ensureColorSpanContrast(text, Color.BLACK);
+        Object[] spans = result.getSpans(0, result.length(), Object.class);
+        assertThat(spans).hasLength(3);
+
+        assertThat(result.getSpanStart(spans[0])).isEqualTo(15);
+        assertThat(result.getSpanEnd(spans[0])).isEqualTo(21);
+        assertThat(((ForegroundColorSpan) spans[0]).getForegroundColor()).isEqualTo(Color.YELLOW);
+
+        assertThat(result.getSpanStart(spans[1])).isEqualTo(0);
+        assertThat(result.getSpanEnd(spans[1])).isEqualTo(31);
+        assertThat(spans[1]).isNotSameInstanceAs(taSpan);  // don't mutate the existing span
+        assertThat(((TextAppearanceSpan) spans[1]).getFamily()).isEqualTo(taSpan.getFamily());
+        assertThat(((TextAppearanceSpan) spans[1]).getTextColor()).isNull();
+
+        assertThat(result.getSpanStart(spans[2])).isEqualTo(26);
+        assertThat(result.getSpanEnd(spans[2])).isEqualTo(31);
+        assertThat(((ForegroundColorSpan) spans[2]).getForegroundColor()).isEqualTo(Color.GREEN);
+    }
+
+    @Test
+    public void testBuilder_ensureColorSpanContrast_partialLength_adjusted() {
+        int background = 0xFFFF0101;  // Slightly lighter red
+        CharSequence text = new SpannableStringBuilder()
+                .append("text with ")
+                .append("some red", new ForegroundColorSpan(Color.RED),
+                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        CharSequence result = ensureColorSpanContrast(text, background);
+
+        // ensure the span has been updated to have > 1.3:1 contrast ratio with fill color
+        Object[] spans = ((Spannable) result).getSpans(0, result.length(), Object.class);
+        assertThat(spans).hasLength(1);
+        int foregroundColor = ((ForegroundColorSpan) spans[0]).getForegroundColor();
+        assertContrastIsWithinRange(foregroundColor, background, 3, 3.2);
+    }
+
+    @Test
+    public void testBuilder_ensureColorSpanContrast_worksWithComplexInput() {
+        Spannable text = new SpannableString("blue text with yellow and green and cyan");
+        text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(new ForegroundColorSpan(Color.BLUE), 0, text.length(),
+                Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        // cyan TextAppearanceSpan
+        TextAppearanceSpan taSpan = new TextAppearanceSpan(mContext,
+                R.style.TextAppearance_DeviceDefault_Notification_Title);
+        taSpan = new TextAppearanceSpan(taSpan.getFamily(), taSpan.getTextStyle(),
+                taSpan.getTextSize(), ColorStateList.valueOf(Color.CYAN), null);
+        text.setSpan(taSpan, 36, 40,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        Spannable result = (Spannable) ensureColorSpanContrast(text, Color.GRAY);
+        Object[] spans = result.getSpans(0, result.length(), Object.class);
+        assertThat(spans).hasLength(3);
+
+        assertThat(result.getSpanStart(spans[0])).isEqualTo(15);
+        assertThat(result.getSpanEnd(spans[0])).isEqualTo(21);
+        assertThat(((ForegroundColorSpan) spans[0]).getForegroundColor()).isEqualTo(Color.YELLOW);
+
+        assertThat(result.getSpanStart(spans[1])).isEqualTo(36);
+        assertThat(result.getSpanEnd(spans[1])).isEqualTo(40);
+        assertThat(spans[1]).isNotSameInstanceAs(taSpan);  // don't mutate the existing span
+        assertThat(((TextAppearanceSpan) spans[1]).getFamily()).isEqualTo(taSpan.getFamily());
+        ColorStateList newCyanList = ((TextAppearanceSpan) spans[1]).getTextColor();
+        assertThat(newCyanList).isNotNull();
+        assertContrastIsWithinRange(newCyanList.getDefaultColor(), Color.GRAY, 3, 3.2);
+
+        assertThat(result.getSpanStart(spans[2])).isEqualTo(26);
+        assertThat(result.getSpanEnd(spans[2])).isEqualTo(31);
+        int newGreen = ((ForegroundColorSpan) spans[2]).getForegroundColor();
+        assertThat(newGreen).isNotEqualTo(Color.GREEN);
+        assertContrastIsWithinRange(newGreen, Color.GRAY, 3, 3.2);
+    }
+
+    @Test
+    public void testBuilder_ensureButtonFillContrast_adjustsDarker() {
+        int background = Color.LTGRAY;
+        int foreground = Color.LTGRAY;
+        int result = Notification.Builder.ensureButtonFillContrast(foreground, background);
+        assertContrastIsWithinRange(result, background, 1.3, 1.5);
+        assertThat(ContrastColorUtil.calculateLuminance(result))
+                .isLessThan(ContrastColorUtil.calculateLuminance(background));
+    }
+
+    @Test
+    public void testBuilder_ensureButtonFillContrast_adjustsLighter() {
+        int background = Color.DKGRAY;
+        int foreground = Color.DKGRAY;
+        int result = Notification.Builder.ensureButtonFillContrast(foreground, background);
+        assertContrastIsWithinRange(result, background, 1.3, 1.5);
+        assertThat(ContrastColorUtil.calculateLuminance(result))
+                .isGreaterThan(ContrastColorUtil.calculateLuminance(background));
+    }
+
+    @Test
     public void testColors_ensureColors_dayMode_producesValidPalette() {
         Notification.Colors c = new Notification.Colors();
         boolean colorized = false;
@@ -399,6 +568,8 @@
             assertEquals(cDay.getSecondaryTextColor(), cNight.getSecondaryTextColor());
             assertEquals(cDay.getPrimaryAccentColor(), cNight.getPrimaryAccentColor());
             assertEquals(cDay.getSecondaryAccentColor(), cNight.getSecondaryAccentColor());
+            assertEquals(cDay.getTertiaryAccentColor(), cNight.getTertiaryAccentColor());
+            assertEquals(cDay.getOnAccentTextColor(), cNight.getOnAccentTextColor());
             assertEquals(cDay.getProtectionColor(), cNight.getProtectionColor());
             assertEquals(cDay.getContrastColor(), cNight.getContrastColor());
             assertEquals(cDay.getRippleAlpha(), cNight.getRippleAlpha());
@@ -413,30 +584,26 @@
         assertThat(c.getSecondaryTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
         assertThat(c.getPrimaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
         assertThat(c.getSecondaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
+        assertThat(c.getTertiaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
+        assertThat(c.getOnAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
         assertThat(c.getErrorColor()).isNotEqualTo(Notification.COLOR_INVALID);
         assertThat(c.getContrastColor()).isNotEqualTo(Notification.COLOR_INVALID);
         assertThat(c.getRippleAlpha()).isAtLeast(0x00);
         assertThat(c.getRippleAlpha()).isAtMost(0xff);
 
-        // Assert that various colors have sufficient contrast
+        // Assert that various colors have sufficient contrast with the background
         assertContrastIsAtLeast(c.getPrimaryTextColor(), c.getBackgroundColor(), 4.5);
         assertContrastIsAtLeast(c.getSecondaryTextColor(), c.getBackgroundColor(), 4.5);
         assertContrastIsAtLeast(c.getPrimaryAccentColor(), c.getBackgroundColor(), 4.5);
         assertContrastIsAtLeast(c.getErrorColor(), c.getBackgroundColor(), 4.5);
         assertContrastIsAtLeast(c.getContrastColor(), c.getBackgroundColor(), 4.5);
 
-        // This accent color is only used for emphasized buttons
+        // These colors are only used for emphasized buttons; they do not need contrast
         assertContrastIsAtLeast(c.getSecondaryAccentColor(), c.getBackgroundColor(), 1);
-    }
+        assertContrastIsAtLeast(c.getTertiaryAccentColor(), c.getBackgroundColor(), 1);
 
-    private void assertContrastIsAtLeast(int foreground, int background, double minContrast) {
-        try {
-            assertThat(calculateContrast(foreground, background)).isAtLeast(minContrast);
-        } catch (AssertionError e) {
-            throw new AssertionError(
-                    String.format("Insufficient contrast: foreground=#%08x background=#%08x",
-                            foreground, background), e);
-        }
+        // The text that is used within the accent color DOES need to have contrast
+        assertContrastIsAtLeast(c.getOnAccentTextColor(), c.getTertiaryAccentColor(), 4.5);
     }
 
     private void resolveColorsInNightMode(boolean nightMode, Notification.Colors c, int rawColor,
diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java
index 22f6ec0..701e619 100644
--- a/core/tests/coretests/src/android/graphics/FontListParserTest.java
+++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java
@@ -27,6 +27,7 @@
 
 import static junit.framework.Assert.fail;
 
+import android.graphics.fonts.FontCustomizationParser;
 import android.graphics.fonts.FontStyle;
 import android.os.LocaleList;
 import android.text.FontConfig;
@@ -46,6 +47,7 @@
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
+import java.util.List;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -318,6 +320,52 @@
         }
     }
 
+    @Test
+    public void alias() throws Exception {
+        String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+                + "<familyset>"
+                + "  <family name='sans-serif'>"
+                + "    <font>test.ttf</font>"
+                + "  </family>"
+                + "  <family name='custom-family'>"
+                + "    <font>missing.ttf</font>"
+                + "  </family>"
+                + "  <alias name='custom-alias' to='sans-serif'/>"
+                + "</familyset>";
+        FontConfig config = readFamilies(xml, true /* include non-existing font files */);
+        List<FontConfig.Alias> aliases = config.getAliases();
+        assertThat(aliases.size()).isEqualTo(1);
+        assertThat(aliases.get(0).getName()).isEqualTo("custom-alias");
+        assertThat(aliases.get(0).getOriginal()).isEqualTo("sans-serif");
+    }
+
+    @Test
+    public void dropped_FamilyAlias() throws Exception {
+        String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+                + "<familyset>"
+                + "  <family name='sans-serif'>"
+                + "    <font>test.ttf</font>"
+                + "  </family>"
+                + "  <family name='custom-family'>"
+                + "    <font>missing.ttf</font>"
+                + "  </family>"
+                + "  <alias name='custom-alias' to='custom-family'/>"
+                + "</familyset>";
+        FontConfig config = readFamilies(xml, false /* exclude not existing file */);
+        assertThat(config.getAliases()).isEmpty();
+    }
+
+    private FontConfig readFamilies(String xml, boolean allowNonExisting)
+            throws IOException, XmlPullParserException {
+        ByteArrayInputStream buffer = new ByteArrayInputStream(
+                xml.getBytes(StandardCharsets.UTF_8));
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(buffer, "UTF-8");
+        parser.nextTag();
+        return FontListParser.readFamilies(parser, "", new FontCustomizationParser.Result(), null,
+                0L /* last modified date */, 0 /* config version */, allowNonExisting);
+    }
+
     private FontConfig.FontFamily readFamily(String xml)
             throws IOException, XmlPullParserException {
         ByteArrayInputStream buffer = new ByteArrayInputStream(
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index dfc9013..149f58f 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -32,6 +32,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -121,6 +122,46 @@
         Mockito.verifyZeroInteractions(mListener);
     }
 
+    @Test
+    public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreNoOtherListeners()
+            throws RemoteException {
+        mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks();
+        Mockito.verify(mDisplayManager)
+                .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS));
+
+        mDisplayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks();
+        Mockito.verify(mDisplayManager)
+                .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(0L));
+
+    }
+
+    @Test
+    public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
+            throws RemoteException {
+        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+                DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+        InOrder inOrder = Mockito.inOrder(mDisplayManager);
+
+        inOrder.verify(mDisplayManager)
+                .registerCallbackWithEventMask(mCallbackCaptor.capture(),
+                        eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+
+        mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks();
+        inOrder.verify(mDisplayManager)
+                .registerCallbackWithEventMask(mCallbackCaptor.capture(),
+                        eq(ALL_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+
+        mDisplayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks();
+        inOrder.verify(mDisplayManager)
+                .registerCallbackWithEventMask(mCallbackCaptor.capture(),
+                        eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+
+        mDisplayManagerGlobal.unregisterDisplayListener(mListener);
+        inOrder.verify(mDisplayManager)
+                .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(0L));
+
+    }
+
     private void waitForHandler() {
         mHandler.runWithScissors(() -> { }, 0);
     }
diff --git a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
index a5a98a9..109b7ab 100644
--- a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
+++ b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
@@ -17,6 +17,8 @@
 package android.service.notification;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNotSame;
 import static junit.framework.Assert.assertNull;
 
 import static org.junit.Assert.assertFalse;
@@ -51,6 +53,7 @@
 
     private final Context mMockContext = mock(Context.class);
     @Mock
+    private Context mRealContext;
     private PackageManager mPm;
 
     private static final String PKG = "com.example.o";
@@ -75,6 +78,8 @@
                 InstrumentationRegistry.getContext().getResources());
         when(mMockContext.getPackageManager()).thenReturn(mPm);
         when(mMockContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+
+        mRealContext = InstrumentationRegistry.getContext();
     }
 
     @Test
@@ -199,6 +204,19 @@
 
     }
 
+    @Test
+    public void testGetPackageContext_worksWithUserAll() {
+        String pkg = "com.android.systemui";
+        int uid = 1000;
+        Notification notification = getNotificationBuilder(GROUP_ID_1, CHANNEL_ID).build();
+        StatusBarNotification sbn = new StatusBarNotification(
+                pkg, pkg, ID, TAG, uid, uid, notification, UserHandle.ALL, null, UID);
+        Context resultContext = sbn.getPackageContext(mRealContext);
+        assertNotNull(resultContext);
+        assertNotSame(mRealContext, resultContext);
+        assertEquals(pkg, resultContext.getPackageName());
+    }
+
     private StatusBarNotification getNotification(String pkg, String group, String channelId) {
         return getNotification(pkg, getNotificationBuilder(group, channelId));
     }
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index 020f4a0..073e468 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -23,11 +23,13 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.content.res.Configuration;
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
 import android.view.IWindowManager;
@@ -38,6 +40,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 /**
  * Tests for {@link WindowContextController}
@@ -53,15 +57,18 @@
 @Presubmit
 public class WindowContextControllerTest {
     private WindowContextController mController;
+    @Mock
     private IWindowManager mMockWms;
+    @Mock
+    private WindowTokenClient mMockToken;
 
     @Before
     public void setUp() throws Exception {
-        mMockWms = mock(IWindowManager.class);
-        mController = new WindowContextController(new Binder(), mMockWms);
-
-        doReturn(true).when(mMockWms).attachWindowContextToDisplayArea(any(), anyInt(),
-                anyInt(), any());
+        MockitoAnnotations.initMocks(this);
+        mController = new WindowContextController(mMockToken, mMockWms);
+        doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt());
+        doReturn(new Configuration()).when(mMockWms).attachWindowContextToDisplayArea(any(),
+                anyInt(), anyInt(), any());
     }
 
     @Test(expected = IllegalStateException.class)
@@ -85,6 +92,7 @@
                 null /* options */);
 
         assertThat(mController.mAttachedToDisplayArea).isTrue();
+        verify(mMockToken).onConfigurationChanged(any(), eq(DEFAULT_DISPLAY));
 
         mController.detachIfNeeded();
 
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 464412f..b7dc1c5 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -23,6 +23,8 @@
 import android.os.BatteryStats;
 import android.os.BatteryStats.HistoryItem;
 import android.os.BatteryStats.Uid.Sensor;
+import android.os.Process;
+import android.os.UserHandle;
 import android.os.WorkSource;
 import android.util.SparseLongArray;
 import android.view.Display;
@@ -53,6 +55,8 @@
 public class BatteryStatsNoteTest extends TestCase {
 
     private static final int UID = 10500;
+    private static final int ISOLATED_APP_ID = Process.FIRST_ISOLATED_UID + 23;
+    private static final int ISOLATED_UID = UserHandle.getUid(0, ISOLATED_APP_ID);
     private static final WorkSource WS = new WorkSource(UID);
 
     /**
@@ -114,6 +118,88 @@
         assertEquals(120_000, bgTime);
     }
 
+    /**
+     * Test BatteryStatsImpl.Uid.noteStartWakeLocked for an isolated uid.
+     */
+    @SmallTest
+    public void testNoteStartWakeLocked_isolatedUid() throws Exception {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+        int pid = 10;
+        String name = "name";
+        String historyName = "historyName";
+
+        WorkSource.WorkChain isolatedWorkChain = new WorkSource.WorkChain();
+        isolatedWorkChain.addNode(ISOLATED_UID, name);
+
+        // Map ISOLATED_UID to UID.
+        bi.addIsolatedUidLocked(ISOLATED_UID, UID);
+
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+        bi.noteStartWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+                WAKE_TYPE_PARTIAL, false);
+
+        clocks.realtime = clocks.uptime = 100;
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+        clocks.realtime = clocks.uptime = 220;
+        bi.noteStopWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+                WAKE_TYPE_PARTIAL);
+
+        // ISOLATED_UID wakelock time should be attributed to UID.
+        BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID)
+                .getAggregatedPartialWakelockTimer();
+        long actualTime = aggregTimer.getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+        long bgTime = aggregTimer.getSubTimer().getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+        assertEquals(220_000, actualTime);
+        assertEquals(120_000, bgTime);
+    }
+
+    /**
+     * Test BatteryStatsImpl.Uid.noteStartWakeLocked for an isolated uid, with a race where the
+     * isolated uid is removed from batterystats before the wakelock has been stopped.
+     */
+    @SmallTest
+    public void testNoteStartWakeLocked_isolatedUidRace() throws Exception {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+        int pid = 10;
+        String name = "name";
+        String historyName = "historyName";
+
+        WorkSource.WorkChain isolatedWorkChain = new WorkSource.WorkChain();
+        isolatedWorkChain.addNode(ISOLATED_UID, name);
+
+        // Map ISOLATED_UID to UID.
+        bi.addIsolatedUidLocked(ISOLATED_UID, UID);
+
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+        bi.noteStartWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+                WAKE_TYPE_PARTIAL, false);
+
+        clocks.realtime = clocks.uptime = 100;
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+        clocks.realtime = clocks.uptime = 150;
+        bi.maybeRemoveIsolatedUidLocked(ISOLATED_UID, clocks.realtime, clocks.uptime);
+
+        clocks.realtime = clocks.uptime = 220;
+        bi.noteStopWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+                WAKE_TYPE_PARTIAL);
+
+        // ISOLATED_UID wakelock time should be attributed to UID.
+        BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID)
+                .getAggregatedPartialWakelockTimer();
+        long actualTime = aggregTimer.getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+        long bgTime = aggregTimer.getSubTimer().getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+        assertEquals(220_000, actualTime);
+        assertEquals(120_000, bgTime);
+    }
+
 
     /**
      * Test BatteryStatsImpl.noteUidProcessStateLocked.
diff --git a/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java
new file mode 100644
index 0000000..cfe660c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 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.internal.util;
+
+import static androidx.core.graphics.ColorUtils.calculateContrast;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Color;
+
+import androidx.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+public class ContrastColorUtilTest extends TestCase {
+
+    @SmallTest
+    public void testEnsureTextContrastAgainstDark() {
+        int darkBg = 0xFF35302A;
+
+        int blueContrastColor = ContrastColorUtil.ensureTextContrast(Color.BLUE, darkBg, true);
+        assertContrastIsWithinRange(blueContrastColor, darkBg, 4.5, 4.75);
+
+        int redContrastColor = ContrastColorUtil.ensureTextContrast(Color.RED, darkBg, true);
+        assertContrastIsWithinRange(redContrastColor, darkBg, 4.5, 4.75);
+
+        final int darkGreen = 0xff008800;
+        int greenContrastColor = ContrastColorUtil.ensureTextContrast(darkGreen, darkBg, true);
+        assertContrastIsWithinRange(greenContrastColor, darkBg, 4.5, 4.75);
+
+        int grayContrastColor = ContrastColorUtil.ensureTextContrast(Color.DKGRAY, darkBg, true);
+        assertContrastIsWithinRange(grayContrastColor, darkBg, 4.5, 4.75);
+
+        int selfContrastColor = ContrastColorUtil.ensureTextContrast(darkBg, darkBg, true);
+        assertContrastIsWithinRange(selfContrastColor, darkBg, 4.5, 4.75);
+    }
+
+    @SmallTest
+    public void testEnsureTextContrastAgainstLight() {
+        int lightBg = 0xFFFFF8F2;
+
+        final int lightBlue = 0xff8888ff;
+        int blueContrastColor = ContrastColorUtil.ensureTextContrast(lightBlue, lightBg, false);
+        assertContrastIsWithinRange(blueContrastColor, lightBg, 4.5, 4.75);
+
+        int redContrastColor = ContrastColorUtil.ensureTextContrast(Color.RED, lightBg, false);
+        assertContrastIsWithinRange(redContrastColor, lightBg, 4.5, 4.75);
+
+        int greenContrastColor = ContrastColorUtil.ensureTextContrast(Color.GREEN, lightBg, false);
+        assertContrastIsWithinRange(greenContrastColor, lightBg, 4.5, 4.75);
+
+        int grayContrastColor = ContrastColorUtil.ensureTextContrast(Color.LTGRAY, lightBg, false);
+        assertContrastIsWithinRange(grayContrastColor, lightBg, 4.5, 4.75);
+
+        int selfContrastColor = ContrastColorUtil.ensureTextContrast(lightBg, lightBg, false);
+        assertContrastIsWithinRange(selfContrastColor, lightBg, 4.5, 4.75);
+    }
+
+    public static void assertContrastIsWithinRange(int foreground, int background,
+            double minContrast, double maxContrast) {
+        assertContrastIsAtLeast(foreground, background, minContrast);
+        assertContrastIsAtMost(foreground, background, maxContrast);
+    }
+
+    public static void assertContrastIsAtLeast(int foreground, int background, double minContrast) {
+        try {
+            assertThat(calculateContrast(foreground, background)).isAtLeast(minContrast);
+        } catch (AssertionError e) {
+            throw new AssertionError(
+                    String.format("Insufficient contrast: foreground=#%08x background=#%08x",
+                            foreground, background), e);
+        }
+    }
+
+    public static void assertContrastIsAtMost(int foreground, int background, double maxContrast) {
+        try {
+            assertThat(calculateContrast(foreground, background)).isAtMost(maxContrast);
+        } catch (AssertionError e) {
+            throw new AssertionError(
+                    String.format("Excessive contrast: foreground=#%08x background=#%08x",
+                            foreground, background), e);
+        }
+    }
+
+}
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 93a336e..96b3325 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -25,6 +25,7 @@
 import android.os.Build;
 import android.os.LocaleList;
 import android.text.FontConfig;
+import android.util.ArraySet;
 import android.util.Xml;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -37,6 +38,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.regex.Pattern;
 
 /**
@@ -120,7 +122,23 @@
         }
     }
 
-    private static FontConfig readFamilies(
+    /**
+     * Parses the familyset tag in font.xml
+     * @param parser a XML pull parser
+     * @param fontDir A system font directory, e.g. "/system/fonts"
+     * @param customization A OEM font customization
+     * @param updatableFontMap A map of updated font files
+     * @param lastModifiedDate A date that the system font is updated.
+     * @param configVersion A version of system font config.
+     * @param allowNonExistingFile true if allowing non-existing font files during parsing fonts.xml
+     * @return result of fonts.xml
+     *
+     * @throws XmlPullParserException
+     * @throws IOException
+     *
+     * @hide
+     */
+    public static FontConfig readFamilies(
             @NonNull XmlPullParser parser,
             @NonNull String fontDir,
             @NonNull FontCustomizationParser.Result customization,
@@ -159,7 +177,24 @@
         }
 
         families.addAll(oemNamedFamilies.values());
-        return new FontConfig(families, aliases, lastModifiedDate, configVersion);
+
+        // Filters aliases that point to non-existing families.
+        Set<String> namedFamilies = new ArraySet<>();
+        for (int i = 0; i < families.size(); ++i) {
+            String name = families.get(i).getName();
+            if (name != null) {
+                namedFamilies.add(name);
+            }
+        }
+        List<FontConfig.Alias> filtered = new ArrayList<>();
+        for (int i = 0; i < aliases.size(); ++i) {
+            FontConfig.Alias alias = aliases.get(i);
+            if (namedFamilies.contains(alias.getOriginal())) {
+                filtered.add(alias);
+            }
+        }
+
+        return new FontConfig(families, filtered, lastModifiedDate, configVersion);
     }
 
     private static boolean keepReading(XmlPullParser parser)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index ba0ab6d..656bdff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -336,6 +336,13 @@
     }
 
     @Override
+    public void onImeDrawnOnTask(int taskId) {
+        if (mStartingWindow != null) {
+            mStartingWindow.onImeDrawnOnTask(taskId);
+        }
+    }
+
+    @Override
     public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
         synchronized (mLock) {
             onTaskAppeared(new TaskAppearedInfo(taskInfo, leash));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index c0df06f..ac97c8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -576,20 +576,17 @@
                 mBubbleContainer.setActiveController(mStackAnimationController);
                 hideFlyoutImmediate();
 
-                if (!mPositioner.showingInTaskbar()) {
-                    // Also, save the magnetized stack so we can dispatch touch events to it.
-                    mMagnetizedObject = mStackAnimationController.getMagnetizedStack(
-                            mMagneticTarget);
-                    mMagnetizedObject.setMagnetListener(mStackMagnetListener);
-                } else {
+                if (mPositioner.showingInTaskbar()) {
                     // In taskbar, the stack isn't draggable so we shouldn't dispatch touch events.
                     mMagnetizedObject = null;
+                } else {
+                    // Save the magnetized stack so we can dispatch touch events to it.
+                    mMagnetizedObject = mStackAnimationController.getMagnetizedStack();
+                    mMagnetizedObject.clearAllTargets();
+                    mMagnetizedObject.addTarget(mMagneticTarget);
+                    mMagnetizedObject.setMagnetListener(mStackMagnetListener);
                 }
 
-                // Also, save the magnetized stack so we can dispatch touch events to it.
-                mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
-                mMagnetizedObject.setMagnetListener(mStackMagnetListener);
-
                 mIsDraggingStack = true;
 
                 // Cancel animations to make the stack temporarily invisible, since we're now
@@ -881,7 +878,6 @@
                         mRelativeStackPositionBeforeRotation = null;
                     }
 
-                    setUpDismissView();
                     if (mIsExpanded) {
                         // Re-draw bubble row and pointer for new orientation.
                         beforeExpandedViewAnimation();
@@ -1043,10 +1039,9 @@
                 contentResolver, "bubble_dismiss_radius", mBubbleSize * 2 /* default */);
 
         // Save the MagneticTarget instance for the newly set up view - we'll add this to the
-        // MagnetizedObjects.
+        // MagnetizedObjects when the dismiss view gets shown.
         mMagneticTarget = new MagnetizedObject.MagneticTarget(
                 mDismissView.getCircle(), dismissRadius);
-
         mBubbleContainer.bringToFront();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 0802fb5..636e145 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -1024,11 +1024,9 @@
     }
 
     /**
-     * Returns the {@link MagnetizedObject} instance for the bubble stack, with the provided
-     * {@link MagnetizedObject.MagneticTarget} added as a target.
+     * Returns the {@link MagnetizedObject} instance for the bubble stack.
      */
-    public MagnetizedObject<StackAnimationController> getMagnetizedStack(
-            MagnetizedObject.MagneticTarget target) {
+    public MagnetizedObject<StackAnimationController> getMagnetizedStack() {
         if (mMagnetizedStack == null) {
             mMagnetizedStack = new MagnetizedObject<StackAnimationController>(
                     mLayout.getContext(),
@@ -1053,7 +1051,6 @@
                     loc[1] = (int) mStackPosition.y;
                 }
             };
-            mMagnetizedStack.addTarget(target);
             mMagnetizedStack.setHapticsEnabled(true);
             mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
index 9f6dd1f..9e01259 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
@@ -303,6 +303,13 @@
     }
 
     /**
+     * Removes all associated targets from this object.
+     */
+    fun clearAllTargets() {
+        associatedTargets.clear()
+    }
+
+    /**
      * Provide this method with all motion events that move the magnetized object. If the
      * location of the motion events moves within the magnetic field of a target, or indicate a
      * fling-to-target gesture, this method will return true and you should not move the object
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
index d327470..9e1c61a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
@@ -23,6 +23,7 @@
 import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.view.ContextThemeWrapper;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.animation.LinearInterpolator;
@@ -33,7 +34,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.view.ContextThemeWrapper;
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 954ca14..e511bff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -445,6 +445,9 @@
         mOneHandedSettingsUtil.registerSettingsKeyObserver(
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
                 mContext.getContentResolver(), mShortcutEnabledObserver, newUserId);
+        mOneHandedSettingsUtil.registerSettingsKeyObserver(
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+                mContext.getContentResolver(), mShortcutEnabledObserver, newUserId);
     }
 
     private void unregisterSettingObservers() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index 7cf4fb7..ff333c8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -171,9 +171,22 @@
      * @return true if user enabled one-handed shortcut in settings, false otherwise.
      */
     public boolean getShortcutEnabled(ContentResolver resolver, int userId) {
-        final String targets = Settings.Secure.getStringForUser(resolver,
+        // Checks SOFTWARE_SHORTCUT_KEY
+        final String targetsSwKey = Settings.Secure.getStringForUser(resolver,
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId);
-        return TextUtils.isEmpty(targets) ? false : targets.contains(ONE_HANDED_MODE_TARGET_NAME);
+        if (!TextUtils.isEmpty(targetsSwKey) && targetsSwKey.contains(
+                ONE_HANDED_MODE_TARGET_NAME)) {
+            return true;
+        }
+
+        // Checks HARDWARE_SHORTCUT_KEY
+        final String targetsHwKey = Settings.Secure.getStringForUser(resolver,
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId);
+        if (!TextUtils.isEmpty(targetsHwKey) && targetsHwKey.contains(
+                ONE_HANDED_MODE_TARGET_NAME)) {
+            return true;
+        }
+        return false;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
index f58c6b1..81dd60d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
@@ -32,6 +32,7 @@
 import android.content.res.TypedArray;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.SurfaceControl;
@@ -44,7 +45,6 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
-import androidx.appcompat.view.ContextThemeWrapper;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.R;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
index a5e96d1..20021eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
@@ -28,6 +28,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.util.Log;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
@@ -45,7 +46,7 @@
 class SizeCompatUILayout {
     private static final String TAG = "SizeCompatUILayout";
 
-    private final SyncTransactionQueue mSyncQueue;
+    final SyncTransactionQueue mSyncQueue;
     private final SizeCompatUIController.SizeCompatUICallback mCallback;
     private Context mContext;
     private Configuration mTaskConfig;
@@ -306,6 +307,10 @@
 
     private void updateSurfacePosition(SurfaceControl leash, int positionX, int positionY) {
         mSyncQueue.runInSync(t -> {
+            if (!leash.isValid()) {
+                Log.w(TAG, "The leash has been released.");
+                return;
+            }
             t.setPosition(leash, positionX, positionY);
             // The size compat UI should be the topmost child of the Task in case there can be more
             // than one children.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
index f634c45..82f69c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
@@ -110,7 +110,8 @@
         }
 
         if (mLeash != null) {
-            new SurfaceControl.Transaction().remove(mLeash).apply();
+            final SurfaceControl leash = mLeash;
+            mLayout.mSyncQueue.runInSync(t -> t.remove(leash));
             mLeash = null;
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index fc7c86d..147f5e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -545,6 +545,15 @@
         removeWindowSynced(taskId, null, null, false);
     }
 
+    void onImeDrawnOnTask(int taskId) {
+        final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+        if (record != null && record.mTaskSnapshotWindow != null
+                && record.mTaskSnapshotWindow.hasImeSurface()) {
+            record.mTaskSnapshotWindow.removeImmediately();
+        }
+        mStartingWindowRecords.remove(taskId);
+    }
+
     protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame,
             boolean playRevealAnimation) {
         final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
@@ -572,14 +581,15 @@
                     Slog.e(TAG, "Found empty splash screen, remove!");
                     removeWindowInner(record.mDecorView, false);
                 }
+                mStartingWindowRecords.remove(taskId);
             }
             if (record.mTaskSnapshotWindow != null) {
                 if (DEBUG_TASK_SNAPSHOT) {
                     Slog.v(TAG, "Removing task snapshot window for " + taskId);
                 }
-                record.mTaskSnapshotWindow.remove();
+                record.mTaskSnapshotWindow.scheduleRemove(
+                        () -> mStartingWindowRecords.remove(taskId));
             }
-            mStartingWindowRecords.remove(taskId);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index e84d498..dee21b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -177,6 +177,13 @@
     }
 
     /**
+     * Called when the IME has drawn on the organized task.
+     */
+    public void onImeDrawnOnTask(int taskId) {
+        mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.onImeDrawnOnTask(taskId));
+    }
+
+    /**
      * Called when the content of a task is ready to show, starting window can be removed.
      */
     public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 6052d3d..7229514 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.startingsurface;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.graphics.Color.WHITE;
 import static android.graphics.Color.alpha;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
@@ -64,7 +63,6 @@
 import android.hardware.HardwareBuffer;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.Trace;
 import android.util.MergedConfiguration;
 import android.util.Slog;
@@ -119,7 +117,12 @@
     private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
 
     private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
-    private static final long DELAY_REMOVAL_TIME_IME_VISIBLE = 350;
+    /**
+     * The max delay time in milliseconds for removing the task snapshot window with IME visible.
+     * Ideally the delay time will be shorter when receiving
+     * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
+     */
+    private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 450;
 
     //tmp vars for unused relayout params
     private static final Point TMP_SURFACE_SIZE = new Point();
@@ -138,7 +141,6 @@
     private final RectF mTmpDstFrame = new RectF();
     private final CharSequence mTitle;
     private boolean mHasDrawn;
-    private long mShownTime;
     private boolean mSizeMismatch;
     private final Paint mBackgroundPaint = new Paint();
     private final int mActivityType;
@@ -148,6 +150,8 @@
     private final SurfaceControl.Transaction mTransaction;
     private final Matrix mSnapshotMatrix = new Matrix();
     private final float[] mTmpFloat9 = new float[9];
+    private Runnable mScheduledRunnable;
+    private final boolean mHasImeSurface;
 
     static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken,
             TaskSnapshot snapshot, ShellExecutor splashScreenExecutor,
@@ -216,7 +220,7 @@
             taskDescription.setBackgroundColor(WHITE);
         }
 
-        final long delayRemovalTime = snapshot.hasImeSurface() ? DELAY_REMOVAL_TIME_IME_VISIBLE
+        final long delayRemovalTime = snapshot.hasImeSurface() ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
                 : DELAY_REMOVAL_TIME_GENERAL;
 
         final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
@@ -281,12 +285,17 @@
         mDelayRemovalTime = delayRemovalTime;
         mTransaction = new SurfaceControl.Transaction();
         mClearWindowHandler = clearWindowHandler;
+        mHasImeSurface = snapshot.hasImeSurface();
     }
 
     int getBackgroundColor() {
         return mBackgroundPaint.getColor();
     }
 
+    boolean hasImeSurface() {
+        return mHasImeSurface;
+    }
+
     /**
      * Ask system bar background painter to draw status bar background.
      * @hide
@@ -304,21 +313,26 @@
         mSystemBarBackgroundPainter.drawNavigationBarBackground(c);
     }
 
-    void remove() {
-        final long now = SystemClock.uptimeMillis();
-        if ((now - mShownTime < mDelayRemovalTime)
-                // Show the latest content as soon as possible for unlocking to home.
-                && mActivityType != ACTIVITY_TYPE_HOME) {
-            final long delayTime = mShownTime + mDelayRemovalTime - now;
-            mSplashScreenExecutor.executeDelayed(() -> remove(), delayTime);
-            if (DEBUG) {
-                Slog.d(TAG, "Defer removing snapshot surface in " + delayTime);
-            }
-            return;
+    void scheduleRemove(Runnable onRemove) {
+        if (mScheduledRunnable != null) {
+            mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
+            mScheduledRunnable = null;
         }
+        mScheduledRunnable = () -> {
+            TaskSnapshotWindow.this.removeImmediately();
+            onRemove.run();
+        };
+        mSplashScreenExecutor.executeDelayed(mScheduledRunnable, mDelayRemovalTime);
+        if (DEBUG) {
+            Slog.d(TAG, "Defer removing snapshot surface in " + mDelayRemovalTime);
+        }
+    }
+
+    void removeImmediately() {
+        mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
         try {
             if (DEBUG) {
-                Slog.d(TAG, "Removing snapshot surface, mHasDrawn: " + mHasDrawn);
+                Slog.d(TAG, "Removing taskSnapshot surface, mHasDrawn: " + mHasDrawn);
             }
             mSession.remove(mWindow);
         } catch (RemoteException e) {
@@ -356,7 +370,6 @@
         } else {
             drawSizeMatchSnapshot();
         }
-        mShownTime = SystemClock.uptimeMillis();
         mHasDrawn = true;
         reportDrawn();
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index d536adb..eef0d9b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -15,38 +15,53 @@
  */
 package com.android.wm.shell.startingsurface;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.graphics.ColorSpace;
+import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.testing.TestableContext;
+import android.view.IWindowSession;
+import android.view.InsetsState;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 import android.view.WindowMetrics;
 import android.window.StartingWindowInfo;
+import android.window.TaskSnapshot;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -61,6 +76,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
 
 import java.util.function.IntSupplier;
 
@@ -78,6 +94,7 @@
     private TransactionPool mTransactionPool;
 
     private final Handler mTestHandler = new Handler(Looper.getMainLooper());
+    private ShellExecutor mTestExecutor;
     private final TestableContext mTestContext = new TestContext(
             InstrumentationRegistry.getInstrumentation().getTargetContext());
     TestStartingSurfaceDrawer mStartingSurfaceDrawer;
@@ -138,9 +155,9 @@
 
         doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics();
         doNothing().when(mMockWindowManager).addView(any(), any());
-
-        mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(mTestContext,
-                new HandlerExecutor(mTestHandler), mTransactionPool));
+        mTestExecutor = new HandlerExecutor(mTestHandler);
+        mStartingSurfaceDrawer = spy(
+                new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mTransactionPool));
     }
 
     @Test
@@ -205,6 +222,48 @@
         assertEquals(0, windowColor3.mReuseCount);
     }
 
+    @Test
+    public void testRemoveTaskSnapshotWithImeSurfaceWhenOnImeDrawn() throws Exception {
+        final int taskId = 1;
+        final StartingWindowInfo windowInfo =
+                createWindowInfo(taskId, android.R.style.Theme);
+        TaskSnapshot snapshot = createTaskSnapshot(100, 100, new Point(100, 100),
+                new Rect(0, 0, 0, 50), true /* hasImeSurface */);
+        final IWindowSession session = WindowManagerGlobal.getWindowSession();
+        spyOn(session);
+        doReturn(WindowManagerGlobal.ADD_OKAY).when(session).addToDisplay(
+                any() /* window */, any() /* attrs */,
+                anyInt() /* viewVisibility */, anyInt() /* displayId */,
+                any() /* requestedVisibility */, any() /* outInputChannel */,
+                any() /* outInsetsState */, any() /* outActiveControls */);
+        TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo,
+                mBinder,
+                snapshot, mTestExecutor, () -> {
+                });
+        spyOn(mockSnapshotWindow);
+        try (AutoCloseable mockTaskSnapshotSession = new AutoCloseable() {
+            MockitoSession mockSession = mockitoSession()
+                    .initMocks(this)
+                    .mockStatic(TaskSnapshotWindow.class)
+                    .startMocking();
+            @Override
+            public void close() {
+                mockSession.finishMocking();
+            }
+        }) {
+            when(TaskSnapshotWindow.create(eq(windowInfo), eq(mBinder), eq(snapshot), any(),
+                    any())).thenReturn(mockSnapshotWindow);
+            // Simulate a task snapshot window created with IME snapshot shown.
+            mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, mBinder, snapshot);
+            waitHandlerIdle(mTestHandler);
+
+            // Verify the task snapshot with IME snapshot will be removed when received the real IME
+            // drawn callback.
+            mStartingSurfaceDrawer.onImeDrawnOnTask(1);
+            verify(mockSnapshotWindow).removeImmediately();
+        }
+    }
+
     private StartingWindowInfo createWindowInfo(int taskId, int themeResId) {
         StartingWindowInfo windowInfo = new StartingWindowInfo();
         final ActivityInfo info = new ActivityInfo();
@@ -216,10 +275,27 @@
         taskInfo.taskId = taskId;
         windowInfo.targetActivityInfo = info;
         windowInfo.taskInfo = taskInfo;
+        windowInfo.topOpaqueWindowInsetsState = new InsetsState();
+        windowInfo.mainWindowLayoutParams = new WindowManager.LayoutParams();
+        windowInfo.topOpaqueWindowLayoutParams = new WindowManager.LayoutParams();
         return windowInfo;
     }
 
     private static void waitHandlerIdle(Handler handler) {
         handler.runWithScissors(() -> { }, 0 /* timeout */);
     }
+
+    private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize,
+            Rect contentInsets, boolean hasImeSurface) {
+        final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888,
+                1, HardwareBuffer.USAGE_CPU_READ_RARELY);
+        return new TaskSnapshot(
+                System.currentTimeMillis(),
+                new ComponentName("", ""), buffer,
+                ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
+                Surface.ROTATION_0, taskSize, contentInsets, false,
+                true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
+                0 /* systemUiVisibility */, false /* isTranslucent */,
+                hasImeSurface /* hasImeSurface */);
+    }
 }
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 0c422df..b348a6e 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -341,6 +341,7 @@
     sk_sp<SkImage> snapshot = layerSurface->makeImageSnapshot();
     const auto subset = SkIRect::MakeWH(properties().getWidth(),
                                         properties().getHeight());
+    uint32_t layerSurfaceGenerationId = layerSurface->generationID();
     // If we don't have an ImageFilter just return the snapshot
     if (imageFilter == nullptr) {
         mSnapshotResult.snapshot = snapshot;
@@ -348,9 +349,10 @@
         mSnapshotResult.outOffset = SkIPoint::Make(0.0f, 0.0f);
         mImageFilterClipBounds = clipBounds;
         mTargetImageFilter = nullptr;
-    } else if (mSnapshotResult.snapshot == nullptr ||
-        imageFilter != mTargetImageFilter.get() ||
-        mImageFilterClipBounds != clipBounds) {
+        mTargetImageFilterLayerSurfaceGenerationId = 0;
+    } else if (mSnapshotResult.snapshot == nullptr || imageFilter != mTargetImageFilter.get() ||
+               mImageFilterClipBounds != clipBounds ||
+               mTargetImageFilterLayerSurfaceGenerationId != layerSurfaceGenerationId) {
         // Otherwise create a new snapshot with the given filter and snapshot
         mSnapshotResult.snapshot =
                 snapshot->makeWithFilter(context,
@@ -361,6 +363,7 @@
                                          &mSnapshotResult.outOffset);
         mTargetImageFilter = sk_ref_sp(imageFilter);
         mImageFilterClipBounds = clipBounds;
+        mTargetImageFilterLayerSurfaceGenerationId = layerSurfaceGenerationId;
     }
 
     return mSnapshotResult;
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 45a4f6c..da04762 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -396,6 +396,7 @@
      * SkImageFilter used to create the mSnapshotResult
      */
     sk_sp<SkImageFilter> mTargetImageFilter;
+    uint32_t mTargetImageFilterLayerSurfaceGenerationId = 0;
 
     /**
      * Clip bounds used to create the mSnapshotResult
diff --git a/mms/OWNERS b/mms/OWNERS
index 7f05a2a..2e419c1 100644
--- a/mms/OWNERS
+++ b/mms/OWNERS
@@ -9,10 +9,10 @@
 jminjie@google.com
 satk@google.com
 shuoq@google.com
-nazaninb@google.com
 sarahchin@google.com
 xiaotonj@google.com
 huiwang@google.com
 jayachandranc@google.com
 chinmayd@google.com
 amruthr@google.com
+sasindran@google.com
diff --git a/packages/CarrierDefaultApp/OWNERS b/packages/CarrierDefaultApp/OWNERS
index 0d23f05..a2352e2 100644
--- a/packages/CarrierDefaultApp/OWNERS
+++ b/packages/CarrierDefaultApp/OWNERS
@@ -8,11 +8,11 @@
 jminjie@google.com
 satk@google.com
 shuoq@google.com
-nazaninb@google.com
 sarahchin@google.com
 xiaotonj@google.com
 huiwang@google.com
 jayachandranc@google.com
 chinmayd@google.com
 amruthr@google.com
+sasindran@google.com
 
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 48cdf16..197b7b2 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -31,7 +31,7 @@
             android:directBootAware="true">
 
         <receiver android:name=".TemporaryFileManager"
-            android:exported="true">
+            android:exported="false">
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
             </intent-filter>
@@ -76,7 +76,7 @@
 
         <receiver android:name=".InstallEventReceiver"
                 android:permission="android.permission.INSTALL_PACKAGES"
-                android:exported="true">
+                android:exported="false">
             <intent-filter android:priority="1">
                 <action android:name="com.android.packageinstaller.ACTION_INSTALL_COMMIT" />
             </intent-filter>
@@ -106,14 +106,14 @@
 
         <receiver android:name=".UninstallEventReceiver"
             android:permission="android.permission.INSTALL_PACKAGES"
-            android:exported="true">
+            android:exported="false">
             <intent-filter android:priority="1">
                 <action android:name="com.android.packageinstaller.ACTION_UNINSTALL_COMMIT" />
             </intent-filter>
         </receiver>
 
         <receiver android:name=".PackageInstalledReceiver"
-                android:exported="true">
+                android:exported="false">
             <intent-filter android:priority="1">
                 <action android:name="android.intent.action.PACKAGE_ADDED" />
                 <data android:scheme="package" />
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index 5d7b9bb..cef9014 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -19,6 +19,9 @@
     },
     {
       "name": "CtsPackageUninstallTestCases"
+    },
+    {
+      "name": "PackageInstallerTests"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 72fa25f..bf0dc7b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -157,7 +157,6 @@
     private Network mDefaultNetwork = null;
     private NetworkCapabilities mDefaultNetworkCapabilities = null;
     private final Runnable mCallback;
-    private final boolean mSupportMergedUi;
 
     private WifiInfo mWifiInfo;
     public boolean enabled;
@@ -181,7 +180,6 @@
         mNetworkScoreManager = networkScoreManager;
         mConnectivityManager = connectivityManager;
         mCallback = callback;
-        mSupportMergedUi = false;
     }
 
     public void setListening(boolean listening) {
@@ -223,10 +221,8 @@
                 } else {
                     ssid = getValidSsid(mWifiInfo);
                 }
-                if (mSupportMergedUi) {
-                    isCarrierMerged = mWifiInfo.isCarrierMerged();
-                    subId = mWifiInfo.getSubscriptionId();
-                }
+                isCarrierMerged = mWifiInfo.isCarrierMerged();
+                subId = mWifiInfo.getSubscriptionId();
                 updateRssi(mWifiInfo.getRssi());
                 maybeRequestNetworkScore();
             }
@@ -255,10 +251,8 @@
             } else {
                 ssid = getValidSsid(mWifiInfo);
             }
-            if (mSupportMergedUi) {
-                isCarrierMerged = mWifiInfo.isCarrierMerged();
-                subId = mWifiInfo.getSubscriptionId();
-            }
+            isCarrierMerged = mWifiInfo.isCarrierMerged();
+            subId = mWifiInfo.getSubscriptionId();
             updateRssi(mWifiInfo.getRssi());
             maybeRequestNetworkScore();
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index 6100615..56454e9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -20,10 +20,13 @@
 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNetworkSelectionDisableReason;
 
 import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiInfo;
+import android.os.Bundle;
 import android.os.SystemClock;
 
 import androidx.annotation.VisibleForTesting;
@@ -36,6 +39,23 @@
 
     private static final int INVALID_RSSI = -127;
 
+    /**
+     * The intent action shows network details settings to allow configuration of Wi-Fi.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: The calling package should put the chosen
+     * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into
+     * the {@link #KEY_CHOSEN_WIFIENTRY_KEY}.
+     * <p>
+     * Output: Nothing.
+     */
+    public static final String ACTION_WIFI_DETAILS_SETTINGS =
+            "android.settings.WIFI_DETAILS_SETTINGS";
+    public static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key";
+    public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
+
     static final int[] WIFI_PIE = {
             com.android.internal.R.drawable.ic_wifi_signal_0,
             com.android.internal.R.drawable.ic_wifi_signal_1,
@@ -275,7 +295,42 @@
         return noInternet ? NO_INTERNET_WIFI_PIE[level] : WIFI_PIE[level];
     }
 
+    /**
+     * Wrapper the {@link #getInternetIconResource} for testing compatibility.
+     */
+    public static class InternetIconInjector {
+
+        protected final Context mContext;
+
+        public InternetIconInjector(Context context) {
+            mContext = context;
+        }
+
+        /**
+         * Returns the Internet icon for a given RSSI level.
+         *
+         * @param noInternet True if a connected Wi-Fi network cannot access the Internet
+         * @param level The number of bars to show (0-4)
+         */
+        public Drawable getIcon(boolean noInternet, int level) {
+            return mContext.getDrawable(WifiUtils.getInternetIconResource(level, noInternet));
+        }
+    }
+
     public static boolean isMeteredOverridden(WifiConfiguration config) {
         return config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE;
     }
+
+    /**
+     * Returns the Intent for Wi-Fi network details settings.
+     *
+     * @param key The Wi-Fi entry key
+     */
+    public static Intent getWifiDetailsSettingsIntent(String key) {
+        final Intent intent = new Intent(ACTION_WIFI_DETAILS_SETTINGS);
+        final Bundle bundle = new Bundle();
+        bundle.putString(KEY_CHOSEN_WIFIENTRY_KEY, key);
+        intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle);
+        return intent;
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index 89960cb..7c2b904 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -20,9 +20,12 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.Intent;
 import android.net.NetworkKey;
 import android.net.RssiCurve;
 import android.net.ScoredNetwork;
@@ -36,6 +39,8 @@
 import android.text.format.DateUtils;
 import android.util.ArraySet;
 
+import androidx.test.core.app.ApplicationProvider;
+
 import com.android.settingslib.R;
 
 import org.junit.Before;
@@ -44,7 +49,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
 
 import java.util.ArrayList;
 import java.util.Set;
@@ -69,7 +73,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mContext = RuntimeEnvironment.application;
+        mContext = spy(ApplicationProvider.getApplicationContext());
     }
 
     @Test
@@ -148,6 +152,32 @@
         assertThat(WifiUtils.isMeteredOverridden(mWifiConfig)).isTrue();
     }
 
+    @Test
+    public void getWifiDetailsSettingsIntent_returnsCorrectValues() {
+        final String key = "test_key";
+
+        final Intent intent = WifiUtils.getWifiDetailsSettingsIntent(key);
+
+        assertThat(intent.getAction()).isEqualTo(WifiUtils.ACTION_WIFI_DETAILS_SETTINGS);
+        final Bundle bundle = intent.getBundleExtra(WifiUtils.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+        assertThat(bundle.getString(WifiUtils.KEY_CHOSEN_WIFIENTRY_KEY)).isEqualTo(key);
+    }
+
+    @Test
+    public void testInternetIconInjector_getIcon_returnsCorrectValues() {
+        WifiUtils.InternetIconInjector iconInjector = new WifiUtils.InternetIconInjector(mContext);
+
+        for (int level = 0; level <= 4; level++) {
+            iconInjector.getIcon(false /* noInternet */, level);
+            verify(mContext).getDrawable(
+                    WifiUtils.getInternetIconResource(level, false /* noInternet */));
+
+            iconInjector.getIcon(true /* noInternet */, level);
+            verify(mContext).getDrawable(
+                    WifiUtils.getInternetIconResource(level, true /* noInternet */));
+        }
+    }
+
     private static ArrayList<ScanResult> buildScanResultCache() {
         ArrayList<ScanResult> scanResults = new ArrayList<>();
         for (int i = 0; i < 5; i++) {
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index eb81961..a46d28b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -76,5 +76,6 @@
         Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED,
         Settings.Global.DEVICE_CONFIG_SYNC_DISABLED,
         Settings.Global.POWER_BUTTON_LONG_PRESS,
+        Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 6022608..96f127b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -101,6 +101,7 @@
         Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED,
         Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED,
         Settings.Secure.QS_TILES,
+        Settings.Secure.QS_AUTO_ADDED_TILES,
         Settings.Secure.CONTROLS_ENABLED,
         Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT,
         Settings.Secure.DOZE_ENABLED,
@@ -118,7 +119,6 @@
         Settings.Secure.VR_DISPLAY_MODE,
         Settings.Secure.NOTIFICATION_BADGING,
         Settings.Secure.NOTIFICATION_DISMISS_RTL,
-        Settings.Secure.QS_AUTO_ADDED_TILES,
         Settings.Secure.SCREENSAVER_ENABLED,
         Settings.Secure.SCREENSAVER_COMPONENTS,
         Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 5220a04..84c5feb 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -20,6 +20,7 @@
 import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.NONE_NEGATIVE_LONG_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.PERCENTAGE_INTEGER_VALIDATOR;
 import static android.view.Display.HdrCapabilities.HDR_TYPES;
@@ -140,6 +141,7 @@
                         /* last= */Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT));
         VALIDATORS.put(Global.DISABLE_WINDOW_BLURS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.DEVICE_CONFIG_SYNC_DISABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
     }
 }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index c577868..6cfcb51 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -72,8 +72,9 @@
      * {@hide}
      */
     private static final ArraySet<String> sBroadcastOnRestore;
+    private static final ArraySet<String> sBroadcastOnRestoreSystemUI;
     static {
-        sBroadcastOnRestore = new ArraySet<String>(4);
+        sBroadcastOnRestore = new ArraySet<String>(9);
         sBroadcastOnRestore.add(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
         sBroadcastOnRestore.add(Settings.Secure.ENABLED_VR_LISTENERS);
         sBroadcastOnRestore.add(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
@@ -83,6 +84,9 @@
         sBroadcastOnRestore.add(Settings.Secure.DARK_THEME_CUSTOM_END_TIME);
         sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
         sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
+        sBroadcastOnRestoreSystemUI = new ArraySet<String>(2);
+        sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_TILES);
+        sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_AUTO_ADDED_TILES);
     }
 
     private interface SettingsLookup {
@@ -133,6 +137,7 @@
         // Will we need a post-restore broadcast for this element?
         String oldValue = null;
         boolean sendBroadcast = false;
+        boolean sendBroadcastSystemUI = false;
         final SettingsLookup table;
 
         if (destination.equals(Settings.Secure.CONTENT_URI)) {
@@ -143,10 +148,12 @@
             table = sGlobalLookup;
         }
 
-        if (sBroadcastOnRestore.contains(name)) {
+        sendBroadcast = sBroadcastOnRestore.contains(name);
+        sendBroadcastSystemUI = sBroadcastOnRestoreSystemUI.contains(name);
+
+        if (sendBroadcast || sendBroadcastSystemUI) {
             // TODO: http://b/22388012
             oldValue = table.lookup(cr, name, UserHandle.USER_SYSTEM);
-            sendBroadcast = true;
         }
 
         try {
@@ -193,18 +200,28 @@
         } catch (Exception e) {
             // If we fail to apply the setting, by definition nothing happened
             sendBroadcast = false;
+            sendBroadcastSystemUI = false;
         } finally {
             // If this was an element of interest, send the "we just restored it"
             // broadcast with the historical value now that the new value has
             // been committed and observers kicked off.
-            if (sendBroadcast) {
+            if (sendBroadcast || sendBroadcastSystemUI) {
                 Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
-                        .setPackage("android").addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+                        .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
                         .putExtra(Intent.EXTRA_SETTING_NAME, name)
                         .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, value)
                         .putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, oldValue)
                         .putExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, restoredFromSdkInt);
-                context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
+
+                if (sendBroadcast) {
+                    intent.setPackage("android");
+                    context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
+                }
+                if (sendBroadcastSystemUI) {
+                    intent.setPackage(
+                            context.getString(com.android.internal.R.string.config_systemUi));
+                    context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
+                }
             }
         }
     }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 073b4d0..4ac1938 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -763,9 +763,6 @@
                 Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES,
                 GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_SELECTION_VALUES);
         dumpSetting(s, p,
-                Settings.Global.ANGLE_ALLOWLIST,
-                GlobalSettingsProto.Gpu.ANGLE_ALLOWLIST);
-        dumpSetting(s, p,
                 Settings.Global.ANGLE_EGL_FEATURES,
                 GlobalSettingsProto.Gpu.ANGLE_EGL_FEATURES);
         dumpSetting(s, p,
@@ -1195,6 +1192,9 @@
         dumpSetting(s, p,
                 Settings.Global.POWER_MANAGER_CONSTANTS,
                 GlobalSettingsProto.POWER_MANAGER_CONSTANTS);
+        dumpSetting(s, p,
+                Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS,
+                GlobalSettingsProto.POWER_BUTTON_LONG_PRESS_DURATION_MS);
 
         final long prepaidSetupToken = p.start(GlobalSettingsProto.PREPAID_SETUP);
         dumpSetting(s, p,
@@ -1476,6 +1476,9 @@
                 Settings.Global.USE_OPEN_WIFI_PACKAGE,
                 GlobalSettingsProto.USE_OPEN_WIFI_PACKAGE);
         dumpSetting(s, p,
+                Settings.Global.UWB_ENABLED,
+                GlobalSettingsProto.UWB_ENABLED);
+        dumpSetting(s, p,
                 Settings.Global.VT_IMS_ENABLED,
                 GlobalSettingsProto.VT_IMS_ENABLED);
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 3297937..7db73c6 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -509,7 +509,6 @@
                     Settings.Global.ANGLE_GL_DRIVER_ALL_ANGLE,
                     Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS,
                     Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES,
-                    Settings.Global.ANGLE_ALLOWLIST,
                     Settings.Global.ANGLE_EGL_FEATURES,
                     Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
                     Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS,
@@ -519,6 +518,7 @@
                     Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST,
                     Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST,
                     Settings.Global.UPDATABLE_DRIVER_SPHAL_LIBRARIES,
+                    Settings.Global.UWB_ENABLED,
                     Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX,
                     Settings.Global.GPU_DEBUG_LAYER_APP,
                     Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
diff --git a/packages/SystemUI/res-keyguard/values/donottranslate.xml b/packages/SystemUI/res-keyguard/values/donottranslate.xml
index a4d0ff7..1934457 100644
--- a/packages/SystemUI/res-keyguard/values/donottranslate.xml
+++ b/packages/SystemUI/res-keyguard/values/donottranslate.xml
@@ -21,6 +21,9 @@
     <!-- Skeleton string format for displaying the date when an alarm is set. -->
     <string name="abbrev_wday_month_day_no_year_alarm">EEEMMMd</string>
 
+    <!-- Skeleton string format for displaying the date shorter. -->
+    <string name="abbrev_month_day_no_year">MMMd</string>
+
     <!-- Skeleton string format for displaying the time in 12-hour format. -->
     <string name="clock_12hr_format">hm</string>
 
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 72b027a..098b7e8 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -142,4 +142,8 @@
         <item name="android:shadowColor">@color/keyguard_shadow_color</item>
         <item name="android:shadowRadius">?attr/shadowRadius</item>
     </style>
+
+    <style name="TextAppearance.Keyguard.BottomArea.Button">
+        <item name="android:shadowRadius">0</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/anim/progress_indeterminate_horizontal_rect.xml b/packages/SystemUI/res/anim/progress_indeterminate_horizontal_rect.xml
new file mode 100644
index 0000000..13133cb
--- /dev/null
+++ b/packages/SystemUI/res/anim/progress_indeterminate_horizontal_rect.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<!-- Copy of progress_indeterminate_horizontal_rect2 in frameworks/base/core/res -->
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+    <objectAnimator
+        android:duration="2000"
+        android:propertyXName="translateX"
+        android:pathData="M -197.60001,0 c 14.28182,0 85.07782,0 135.54689,0 c 54.26191,0 90.42461,0 168.24331,0 c 144.72154,0 316.40982,0 316.40982,0 "
+        android:interpolator="@interpolator/progress_indeterminate_horizontal_rect2_translatex_copy"
+        android:repeatCount="infinite" />
+</set>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_state_off.xml b/packages/SystemUI/res/color/settingslib_state_off.xml
new file mode 100644
index 0000000..e821825
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_state_off.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentSecondaryVariant"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_state_on.xml b/packages/SystemUI/res/color/settingslib_state_on.xml
new file mode 100644
index 0000000..6d2133c
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_state_on.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentPrimary"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_track_off.xml b/packages/SystemUI/res/color/settingslib_track_off.xml
new file mode 100644
index 0000000..21d1dcc
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_track_off.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentSecondary"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_track_on.xml b/packages/SystemUI/res/color/settingslib_track_on.xml
new file mode 100644
index 0000000..ba7848a
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_track_on.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentPrimaryVariant"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_arrow_forward.xml b/packages/SystemUI/res/drawable/ic_arrow_forward.xml
new file mode 100644
index 0000000..438e4c7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_forward.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2021 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"
+        android:autoMirrored="true"
+        android:tint="?android:attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/black"
+        android:pathData="M6.23,20.23l1.77,1.77l10,-10l-10,-10l-1.77,1.77l8.23,8.23z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml b/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml
new file mode 100644
index 0000000..2c34060
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:tint="?android:attr/colorControlNormal"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M18,8h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10C20,8.9 19.1,8 18,8zM9,6c0,-1.66 1.34,-3 3,-3s3,1.34 3,3v2H9V6zM18,20H6V10h12V20zM12,17c1.1,0 2,-0.9 2,-2c0,-1.1 -0.9,-2 -2,-2c-1.1,0 -2,0.9 -2,2C10,16.1 10.9,17 12,17z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_media_home_devices.xml b/packages/SystemUI/res/drawable/ic_media_home_devices.xml
new file mode 100644
index 0000000..886c64d9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_home_devices.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M20,4H4c-1.1,0 -2,0.9 -2,2v11c0,1.1 0.9,2 2,2h4v2h3v-4H4V6h16v1h2V6c0,-1.1 -0.9,-2 -2,-2z"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M17.5,16.5m-2.33,0a2.33,2.33 0,1 1,4.66 0a2.33,2.33 0,1 1,-4.66 0"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M21,8h-7c-0.55,0 -1,0.45 -1,1v11c0,0.55 0.45,1 1,1h7c0.55,0 1,-0.45 1,-1L22,9c0,-0.55 -0.45,-1 -1,-1zM17.5,9c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5 -1.5,-0.67 -1.5,-1.5 0.67,-1.5 1.5,-1.5zM17.5,20c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_settings_24dp.xml b/packages/SystemUI/res/drawable/ic_settings_24dp.xml
new file mode 100644
index 0000000..ac4c43b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_settings_24dp.xml
@@ -0,0 +1,29 @@
+<!--
+     Copyright (C) 2015 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.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M13.85,22.25h-3.7c-0.74,0 -1.36,-0.54 -1.45,-1.27l-0.27,-1.89c-0.27,-0.14 -0.53,-0.29 -0.79,-0.46l-1.8,0.72c-0.7,0.26 -1.47,-0.03 -1.81,-0.65L2.2,15.53c-0.35,-0.66 -0.2,-1.44 0.36,-1.88l1.53,-1.19c-0.01,-0.15 -0.02,-0.3 -0.02,-0.46c0,-0.15 0.01,-0.31 0.02,-0.46l-1.52,-1.19C1.98,9.9 1.83,9.09 2.2,8.47l1.85,-3.19c0.34,-0.62 1.11,-0.9 1.79,-0.63l1.81,0.73c0.26,-0.17 0.52,-0.32 0.78,-0.46l0.27,-1.91c0.09,-0.7 0.71,-1.25 1.44,-1.25h3.7c0.74,0 1.36,0.54 1.45,1.27l0.27,1.89c0.27,0.14 0.53,0.29 0.79,0.46l1.8,-0.72c0.71,-0.26 1.48,0.03 1.82,0.65l1.84,3.18c0.36,0.66 0.2,1.44 -0.36,1.88l-1.52,1.19c0.01,0.15 0.02,0.3 0.02,0.46s-0.01,0.31 -0.02,0.46l1.52,1.19c0.56,0.45 0.72,1.23 0.37,1.86l-1.86,3.22c-0.34,0.62 -1.11,0.9 -1.8,0.63l-1.8,-0.72c-0.26,0.17 -0.52,0.32 -0.78,0.46l-0.27,1.91C15.21,21.71 14.59,22.25 13.85,22.25zM13.32,20.72c0,0.01 0,0.01 0,0.02L13.32,20.72zM10.68,20.7l0,0.02C10.69,20.72 10.69,20.71 10.68,20.7zM10.62,20.25h2.76l0.37,-2.55l0.53,-0.22c0.44,-0.18 0.88,-0.44 1.34,-0.78l0.45,-0.34l2.38,0.96l1.38,-2.4l-2.03,-1.58l0.07,-0.56c0.03,-0.26 0.06,-0.51 0.06,-0.78c0,-0.27 -0.03,-0.53 -0.06,-0.78l-0.07,-0.56l2.03,-1.58l-1.39,-2.4l-2.39,0.96l-0.45,-0.35c-0.42,-0.32 -0.87,-0.58 -1.33,-0.77L13.75,6.3l-0.37,-2.55h-2.76L10.25,6.3L9.72,6.51C9.28,6.7 8.84,6.95 8.38,7.3L7.93,7.63L5.55,6.68L4.16,9.07l2.03,1.58l-0.07,0.56C6.09,11.47 6.06,11.74 6.06,12c0,0.26 0.02,0.53 0.06,0.78l0.07,0.56l-2.03,1.58l1.38,2.4l2.39,-0.96l0.45,0.35c0.43,0.33 0.86,0.58 1.33,0.77l0.53,0.22L10.62,20.25zM18.22,17.72c0,0.01 -0.01,0.02 -0.01,0.03L18.22,17.72zM5.77,17.71l0.01,0.02C5.78,17.72 5.77,17.71 5.77,17.71zM3.93,9.47L3.93,9.47C3.93,9.47 3.93,9.47 3.93,9.47zM18.22,6.27c0,0.01 0.01,0.02 0.01,0.02L18.22,6.27zM5.79,6.25L5.78,6.27C5.78,6.27 5.79,6.26 5.79,6.25zM13.31,3.28c0,0.01 0,0.01 0,0.02L13.31,3.28zM10.69,3.26l0,0.02C10.69,3.27 10.69,3.27 10.69,3.26z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M12,12m-3.5,0a3.5,3.5 0,1 1,7 0a3.5,3.5 0,1 1,-7 0"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml b/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml
new file mode 100644
index 0000000..f38a368
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2020 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.0"
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/colorControlNormal">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M2,22l16,0l0,-2l-11,0l13,-13l0,1l2,0l0,-6z"
+        android:strokeAlpha="0.3"
+        android:fillAlpha="0.3"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,10h2v8h-2z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,20h2v2h-2z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/internet_dialog_background.xml b/packages/SystemUI/res/drawable/internet_dialog_background.xml
new file mode 100644
index 0000000..3ceb0f6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+    <shape android:shape="rectangle">
+        <corners android:radius="8dp" />
+        <solid android:color="?android:attr/colorBackground" />
+    </shape>
+</inset>
diff --git a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
new file mode 100644
index 0000000..50267fd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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"
+       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+       android:shape="rectangle">
+    <stroke
+        android:color="?androidprv:attr/colorAccentPrimaryVariant"
+        android:width="1dp"/>
+    <corners android:radius="20dp"/>
+    <padding
+        android:left="8dp"
+        android:right="8dp"
+        android:top="4dp"
+        android:bottom="4dp" />
+    <solid android:color="@android:color/transparent" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml b/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml
new file mode 100644
index 0000000..14672ef
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+    <shape android:shape="rectangle">
+        <corners
+            android:topLeftRadius="@dimen/internet_dialog_corner_radius"
+            android:topRightRadius="@dimen/internet_dialog_corner_radius"
+            android:bottomLeftRadius="@dimen/internet_dialog_corner_radius"
+            android:bottomRightRadius="@dimen/internet_dialog_corner_radius"/>
+        <solid android:color="?android:attr/colorBackground" />
+    </shape>
+</inset>
diff --git a/packages/SystemUI/res/drawable/logout_button_background.xml b/packages/SystemUI/res/drawable/logout_button_background.xml
index eafd663..34434be 100644
--- a/packages/SystemUI/res/drawable/logout_button_background.xml
+++ b/packages/SystemUI/res/drawable/logout_button_background.xml
@@ -17,7 +17,8 @@
   -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
        android:shape="rectangle">
-    <solid android:color="@color/logout_button_bg_color"/>
+    <solid android:color="?androidprv:attr/colorAccentPrimary"/>
     <corners android:radius="@dimen/logout_button_corner_radius"/>
 </shape>
diff --git a/packages/SystemUI/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml b/packages/SystemUI/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml
new file mode 100644
index 0000000..95209f8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<!-- Copy of progress_indeterminate_horizontal_material_trimmed in frameworks/base/core/res -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+                 android:drawable="@drawable/vector_drawable_progress_indeterminate_horizontal_trimmed" >
+    <target
+        android:name="rect_grp"
+        android:animation="@anim/progress_indeterminate_horizontal_rect" />
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml
new file mode 100644
index 0000000..088e82b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2021 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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="@color/settingslib_state_off_color"/>
+            <corners android:radius="@dimen/settingslib_switch_bar_radius"/>
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml
new file mode 100644
index 0000000..250188b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2021 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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="@color/settingslib_state_on_color"/>
+            <corners android:radius="@dimen/settingslib_switch_bar_radius"/>
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml b/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml
new file mode 100644
index 0000000..b41762f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2021 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:top="@dimen/settingslib_switch_thumb_margin"
+        android:left="@dimen/settingslib_switch_thumb_margin"
+        android:right="@dimen/settingslib_switch_thumb_margin"
+        android:bottom="@dimen/settingslib_switch_thumb_margin">
+        <shape android:shape="oval">
+            <size
+                android:height="@dimen/settingslib_switch_thumb_size"
+                android:width="@dimen/settingslib_switch_thumb_size"/>
+            <solid
+                android:color="@color/settingslib_thumb_off_color"
+                android:alpha="?android:attr/disabledAlpha"/>
+        </shape>
+    </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_off.xml b/packages/SystemUI/res/drawable/settingslib_thumb_off.xml
new file mode 100644
index 0000000..87d4aea
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_off.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:top="@dimen/settingslib_switch_thumb_margin"
+        android:bottom="@dimen/settingslib_switch_thumb_margin">
+        <shape android:shape="oval">
+            <size
+                android:height="@dimen/settingslib_switch_thumb_size"
+                android:width="@dimen/settingslib_switch_thumb_size"/>
+            <solid android:color="@color/settingslib_thumb_off_color"/>
+        </shape>
+    </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_on.xml b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml
new file mode 100644
index 0000000..5566ea3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:top="@dimen/settingslib_switch_thumb_margin"
+        android:bottom="@dimen/settingslib_switch_thumb_margin">
+        <shape android:shape="oval">
+            <size
+                android:height="@dimen/settingslib_switch_thumb_size"
+                android:width="@dimen/settingslib_switch_thumb_size"/>
+            <solid android:color="@color/settingslib_state_on_color"/>
+        </shape>
+    </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml b/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml
new file mode 100644
index 0000000..06bb779
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/settingslib_thumb_on" android:state_checked="true"/>
+    <item android:drawable="@drawable/settingslib_thumb_off" android:state_checked="false"/>
+    <item android:drawable="@drawable/settingslib_thumb_disabled" android:state_enabled="false"/>
+</selector>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml b/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml
new file mode 100644
index 0000000..15dfcb7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2021 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"
+    android:width="@dimen/settingslib_switch_track_width"
+    android:height="@dimen/settingslib_switch_track_height">
+    <solid
+        android:color="@color/settingslib_track_off_color"
+        android:alpha="?android:attr/disabledAlpha"/>
+    <corners android:radius="@dimen/settingslib_switch_track_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_off_background.xml b/packages/SystemUI/res/drawable/settingslib_track_off_background.xml
new file mode 100644
index 0000000..3a09284
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_off_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 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"
+    android:width="@dimen/settingslib_switch_track_width"
+    android:height="@dimen/settingslib_switch_track_height">
+    <padding android:left="@dimen/settingslib_switch_thumb_margin"
+             android:right="@dimen/settingslib_switch_thumb_margin"/>
+    <solid android:color="@color/settingslib_track_off_color"/>
+    <corners android:radius="@dimen/settingslib_switch_track_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_on_background.xml b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml
new file mode 100644
index 0000000..1d9dacd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 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"
+    android:width="@dimen/settingslib_switch_track_width"
+    android:height="@dimen/settingslib_switch_track_height">
+    <padding android:left="@dimen/settingslib_switch_thumb_margin"
+             android:right="@dimen/settingslib_switch_thumb_margin"/>
+    <solid android:color="@color/settingslib_track_on_color"/>
+    <corners android:radius="@dimen/settingslib_switch_track_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_selector.xml b/packages/SystemUI/res/drawable/settingslib_track_selector.xml
new file mode 100644
index 0000000..a38c3b4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_selector.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/settingslib_track_on_background" android:state_checked="true"/>
+    <item android:drawable="@drawable/settingslib_track_off_background" android:state_checked="false"/>
+    <item android:drawable="@drawable/settingslib_track_disabled_background" android:state_enabled="false"/>
+</selector>
diff --git a/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml b/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml
new file mode 100644
index 0000000..aec204f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<!-- Variant of vector_drawable_progress_indeterminate_horizontal in frameworks/base/core/res, which
+     draws the whole height of the progress bar instead having blank space above and below the
+     bar. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+        android:height="10dp"
+        android:width="340dp"
+        android:viewportHeight="10"
+        android:viewportWidth="340" >
+    <group
+        android:name="progress_group"
+        android:translateX="180"
+        android:translateY="5" >
+        <path
+            android:name="background_track"
+            android:pathData="M -180.0,-5.0 l 360.0,0 l 0,10.0 l -360.0,0 Z"
+            android:fillColor="?androidprv:attr/colorSurfaceVariant"/>
+        <group
+            android:name="rect_grp"
+            android:translateX="-197.60001"
+            android:scaleX="0.5" >
+            <path
+                android:name="rect"
+                android:pathData="M -144.0,-5.0 l 288.0,0 l 0,10.0 l -288.0,0 Z"
+                android:fillColor="?androidprv:attr/colorAccentPrimaryVariant" />
+        </group>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_actions_toast.xml b/packages/SystemUI/res/layout/global_actions_toast.xml
new file mode 100644
index 0000000..1f08996
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_toast.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center|bottom"
+    android:gravity="center"
+    android:layout_marginBottom="@dimen/global_actions_info_margin"
+    app:layout_constraintBottom_toBottomOf="parent"
+    app:layout_constraintStart_toStartOf="parent"
+    app:layout_constraintEnd_toEndOf="parent"
+    app:layout_constraintWidth_max="382dp"
+    android:layout_weight="0"
+    android:background="@drawable/global_actions_lite_background"
+    android:theme="@style/Theme.SystemUI.QuickSettings"
+    android:paddingTop="14dp"
+    android:paddingBottom="14dp"
+    android:paddingStart="20dp"
+    android:paddingEnd="20dp"
+    android:orientation="horizontal">
+    <TextView
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:textSize="14sp"
+        android:textColor="?android:attr/textColorSecondary"
+        android:text="@string/global_action_smart_lock_disabled" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_screenshot_static.xml b/packages/SystemUI/res/layout/global_screenshot_static.xml
index e4a9694..6a9254c 100644
--- a/packages/SystemUI/res/layout/global_screenshot_static.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_static.xml
@@ -36,7 +36,6 @@
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
-        android:layout_marginBottom="@dimen/screenshot_action_container_offset_y"
         android:paddingEnd="@dimen/screenshot_action_container_padding_right"
         android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
         android:elevation="1dp"
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
new file mode 100644
index 0000000..b841419
--- /dev/null
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -0,0 +1,373 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:id="@+id/internet_connectivity_dialog"
+    android:layout_width="@dimen/internet_dialog_list_max_width"
+    android:layout_height="@dimen/internet_dialog_list_max_height"
+    android:background="@drawable/internet_dialog_rounded_top_corner_background"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@style/Widget.SliceView.Panel"
+        android:gravity="center_vertical|center_horizontal"
+        android:layout_marginTop="24dp"
+        android:layout_marginBottom="@dimen/internet_dialog_network_layout_margin"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/internet_dialog_title"
+            android:gravity="center_vertical|center_horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="32dp"
+            android:textColor="?android:attr/textColorPrimary"
+            android:fontFamily="google-sans"
+            android:textSize="24sp"/>
+
+        <TextView
+            android:id="@+id/internet_dialog_subtitle"
+            android:gravity="center_vertical|center_horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="20dp"
+            android:layout_marginTop="4dp"
+            android:ellipsize="end"
+            android:maxLines="1"
+            android:fontFamily="google-sans"
+            android:textSize="14sp"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/internet_dialog_network_layout_margin"
+        android:orientation="vertical">
+
+        <View
+            android:id="@+id/divider"
+            android:layout_gravity="center_vertical|center_horizontal"
+            android:layout_width="340dp"
+            android:layout_height="4dp"
+            android:background="?androidprv:attr/colorSurfaceVariant"/>
+
+        <ProgressBar
+            android:id="@+id/wifi_searching_progress"
+            android:indeterminate="true"
+            android:layout_width="340dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:visibility="gone"
+            style="@style/TrimmedHorizontalProgressBar"/>
+    </LinearLayout>
+
+    <androidx.core.widget.NestedScrollView
+        android:id="@+id/scroll_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:id="@+id/scroll_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <LinearLayout
+                android:id="@+id/internet_list"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <LinearLayout
+                    android:id="@+id/mobile_network_layout"
+                    android:layout_width="match_parent"
+                    android:layout_height="88dp"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:layout_gravity="center_vertical|start"
+                    android:orientation="horizontal"
+                    android:layout_marginEnd="@dimen/internet_dialog_network_layout_margin"
+                    android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"
+                    android:paddingStart="22dp"
+                    android:paddingEnd="22dp">
+
+                    <FrameLayout
+                        android:layout_width="24dp"
+                        android:layout_height="24dp"
+                        android:clickable="false"
+                        android:layout_gravity="center_vertical|start">
+                        <ImageView
+                            android:id="@+id/signal_icon"
+                            android:autoMirrored="true"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="center"/>
+                    </FrameLayout>
+
+                    <LinearLayout
+                        android:layout_weight="1"
+                        android:id="@+id/mobile_network_list"
+                        android:orientation="vertical"
+                        android:clickable="false"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:gravity="start|center_vertical">
+                        <TextView
+                            android:id="@+id/mobile_title"
+                            android:textDirection="locale"
+                            android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"
+                            android:layout_marginEnd="7dp"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="center_vertical|start"
+                            android:ellipsize="end"
+                            android:textColor="?android:attr/textColorPrimary"
+                            android:textSize="16sp"
+                            android:fontFamily="google-sans"/>
+                        <TextView
+                            android:id="@+id/mobile_summary"
+                            android:textDirection="locale"
+                            android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"
+                            android:layout_marginEnd="34dp"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="center_vertical|start"
+                            android:ellipsize="end"
+                            android:textColor="?android:attr/textColorTertiary"
+                            android:textSize="14sp"
+                            android:fontFamily="google-sans"/>
+                    </LinearLayout>
+
+                    <FrameLayout
+                        android:layout_width="@dimen/settingslib_switch_track_width"
+                        android:layout_height="48dp"
+                        android:layout_gravity="end|center_vertical">
+                        <Switch
+                            android:id="@+id/mobile_toggle"
+                            android:switchMinWidth="@dimen/settingslib_switch_track_width"
+                            android:layout_gravity="center"
+                            android:layout_width="@dimen/settingslib_switch_track_width"
+                            android:layout_height="@dimen/settingslib_switch_track_height"
+                            android:track="@drawable/settingslib_track_selector"
+                            android:thumb="@drawable/settingslib_thumb_selector"
+                            android:theme="@style/MainSwitch.Settingslib"/>
+                    </FrameLayout>
+
+                </LinearLayout>
+
+                <LinearLayout
+                    android:id="@+id/turn_on_wifi_layout"
+                    android:layout_width="match_parent"
+                    android:layout_height="72dp"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:gravity="center"
+                    android:orientation="horizontal"
+                    android:layout_marginEnd="@dimen/internet_dialog_network_layout_margin"
+                    android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"
+                    android:paddingStart="22dp"
+                    android:paddingEnd="22dp">
+
+                    <FrameLayout
+                        android:layout_weight="1"
+                        android:orientation="vertical"
+                        android:clickable="false"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent">
+                        <TextView
+                            android:id="@+id/wifi_toggle_title"
+                            android:text="@string/turn_on_wifi"
+                            android:textDirection="locale"
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="start|center_vertical"
+                            android:textColor="?android:attr/textColorPrimary"
+                            android:textSize="16sp"
+                            android:fontFamily="google-sans"/>
+                    </FrameLayout>
+
+                    <FrameLayout
+                        android:layout_width="@dimen/settingslib_switch_track_width"
+                        android:layout_height="48dp"
+                        android:layout_marginTop="10dp"
+                        android:layout_marginBottom="10dp">
+                        <Switch
+                            android:id="@+id/wifi_toggle"
+                            android:switchMinWidth="@dimen/settingslib_switch_track_width"
+                            android:layout_gravity="center"
+                            android:layout_width="@dimen/settingslib_switch_track_width"
+                            android:layout_height="@dimen/settingslib_switch_track_height"
+                            android:track="@drawable/settingslib_track_selector"
+                            android:thumb="@drawable/settingslib_thumb_selector"
+                            android:theme="@style/MainSwitch.Settingslib"/>
+                    </FrameLayout>
+
+                </LinearLayout>
+
+                <LinearLayout
+                    android:id="@+id/wifi_connected_layout"
+                    android:layout_width="match_parent"
+                    android:layout_height="72dp"
+                    android:layout_gravity="center_vertical|start"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:visibility="gone"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:orientation="horizontal"
+                    android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"
+                    android:layout_marginEnd="@dimen/internet_dialog_network_layout_margin"
+                    android:paddingStart="20dp"
+                    android:paddingEnd="24dp">
+
+                    <FrameLayout
+                        android:layout_width="24dp"
+                        android:layout_height="24dp"
+                        android:clickable="false"
+                        android:layout_gravity="center_vertical|start">
+                        <ImageView
+                            android:id="@+id/wifi_connected_icon"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="center"/>
+                    </FrameLayout>
+
+                    <LinearLayout
+                        android:id="@+id/wifi_connected_list"
+                        android:orientation="vertical"
+                        android:clickable="false"
+                        android:layout_width="wrap_content"
+                        android:layout_height="72dp"
+                        android:layout_marginEnd="30dp"
+                        android:layout_weight="1"
+                        android:gravity="start|center_vertical">
+                        <TextView
+                            android:id="@+id/wifi_connected_title"
+                            android:textDirection="locale"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="center_vertical|start"
+                            android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"
+                            android:ellipsize="end"
+                            android:textColor="?android:attr/textColorPrimary"
+                            android:textSize="14sp"
+                            android:fontFamily="google-sans"/>
+                        <TextView
+                            android:id="@+id/wifi_connected_summary"
+                            android:textDirection="locale"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="center_vertical|start"
+                            android:layout_marginStart="@dimen/internet_dialog_network_layout_margin"
+                            android:ellipsize="end"
+                            android:textColor="?android:attr/textColorTertiary"
+                            android:textSize="14sp"
+                            android:fontFamily="google-sans"/>
+                    </LinearLayout>
+
+                    <FrameLayout
+                        android:layout_width="24dp"
+                        android:layout_height="match_parent"
+                        android:clickable="false"
+                        android:layout_gravity="end|center_vertical"
+                        android:gravity="center">
+                        <ImageView
+                            android:id="@+id/wifi_settings_icon"
+                            android:src="@drawable/ic_settings_24dp"
+                            android:layout_width="24dp"
+                            android:layout_gravity="end|center_vertical"
+                            android:layout_height="wrap_content"/>
+                    </FrameLayout>
+
+                </LinearLayout>
+
+                <androidx.recyclerview.widget.RecyclerView
+                    android:id="@+id/wifi_list_layout"
+                    android:scrollbars="vertical"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:overScrollMode="never"
+                    android:nestedScrollingEnabled="false"/>
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/see_all_layout"
+                android:layout_width="match_parent"
+                android:layout_height="64dp"
+                android:clickable="true"
+                android:focusable="true"
+                android:background="?android:attr/selectableItemBackground"
+                android:gravity="center_vertical|center_horizontal"
+                android:orientation="horizontal"
+                android:paddingStart="22dp"
+                android:paddingEnd="22dp">
+
+                <FrameLayout
+                    android:layout_width="24dp"
+                    android:layout_height="24dp"
+                    android:clickable="false"
+                    android:layout_gravity="center_vertical|start"
+                    android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+                    <ImageView
+                        android:id="@+id/arrow_forward"
+                        android:src="@drawable/ic_arrow_forward"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="center"/>
+                </FrameLayout>
+
+                <FrameLayout
+                    android:orientation="vertical"
+                    android:clickable="false"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+                    <TextView
+                        android:text="@string/see_all_networks"
+                        android:textDirection="locale"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:gravity="start|center_vertical"
+                        android:textColor="?android:attr/textColorPrimary"
+                        android:textSize="14sp"
+                        android:fontFamily="google-sans"/>
+                </FrameLayout>
+
+            </LinearLayout>
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="48dp"
+                android:layout_marginBottom="40dp">
+                <Button
+                    style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+                    android:id="@+id/done"
+                    android:layout_width="67dp"
+                    android:layout_height="36dp"
+                    android:layout_marginEnd="24dp"
+                    android:layout_gravity="end|center_vertical"
+                    android:background="@drawable/internet_dialog_footer_background"
+                    android:textColor="?android:attr/textColorPrimary"
+                    android:text="@string/inline_done_button"
+                    android:textSize="14sp"
+                    android:fontFamily="google-sans"/>
+            </FrameLayout>
+        </LinearLayout>
+    </androidx.core.widget.NestedScrollView>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/internet_list_item.xml b/packages/SystemUI/res/layout/internet_list_item.xml
new file mode 100644
index 0000000..05352c5
--- /dev/null
+++ b/packages/SystemUI/res/layout/internet_list_item.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/internet_container"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/wifi_list"
+        android:layout_width="match_parent"
+        android:layout_height="72dp"
+        android:layout_gravity="center_vertical|start"
+        android:clickable="true"
+        android:focusable="true"
+        android:background="?android:attr/selectableItemBackground"
+        android:orientation="horizontal"
+        android:paddingStart="20dp"
+        android:paddingEnd="40dp">
+        <FrameLayout
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:clickable="false"
+            android:layout_gravity="center_vertical|start"
+            android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+            <ImageView
+                android:id="@+id/wifi_icon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"/>
+        </FrameLayout>
+
+        <LinearLayout
+            android:id="@+id/wifi_network_layout"
+            android:orientation="vertical"
+            android:clickable="false"
+            android:layout_width="wrap_content"
+            android:layout_height="72dp"
+            android:layout_weight="1"
+            android:gravity="start|center_vertical"
+            android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+            <TextView
+                android:id="@+id/wifi_title"
+                android:textDirection="locale"
+                android:layout_width="wrap_content"
+                android:layout_height="20dp"
+                android:gravity="start|center_vertical"
+                android:ellipsize="end"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textSize="14sp"
+                android:fontFamily="google-sans"
+                android:layout_marginEnd="18dp"/>
+            <TextView
+                android:id="@+id/wifi_summary"
+                android:textDirection="locale"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="start|center_vertical"
+                android:ellipsize="end"
+                android:textColor="?android:attr/textColorSecondary"
+                android:textSize="14sp"
+                android:fontFamily="google-sans"
+                android:layout_marginEnd="18dp"/>
+        </LinearLayout>
+
+        <FrameLayout
+            android:layout_width="24dp"
+            android:layout_height="match_parent"
+            android:clickable="false"
+            android:layout_gravity="end|center_vertical">
+            <ImageView
+                android:id="@+id/wifi_end_icon"
+                android:layout_gravity="end|center_vertical"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+        </FrameLayout>
+
+    </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index eb76382..850b017 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -30,7 +30,6 @@
         android:id="@+id/status_icon_area"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:paddingEnd="@dimen/system_icons_keyguard_padding_end"
         android:paddingTop="@dimen/status_bar_padding_top"
         android:layout_alignParentEnd="true"
         android:gravity="center_vertical|end" >
@@ -39,6 +38,7 @@
             android:layout_height="match_parent"
             android:layout_weight="1"
             android:layout_marginStart="@dimen/system_icons_super_container_margin_start"
+            android:layout_marginEnd="@dimen/status_bar_padding_end"
             android:gravity="center_vertical|end">
             <include layout="@layout/system_icons" />
         </FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index 075473a..566cd25 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -163,18 +163,6 @@
         </LinearLayout>
     </LinearLayout>
 
-    <ImageView
-        android:id="@+id/media_seamless_fallback"
-        android:layout_width="@dimen/qs_seamless_fallback_icon_size"
-        android:layout_height="@dimen/qs_seamless_fallback_icon_size"
-        android:layout_marginTop="@dimen/qs_media_padding"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        android:layout_marginStart="@dimen/qs_center_guideline_padding"
-        android:layout_marginEnd="@dimen/qs_seamless_fallback_margin"
-        android:tint="?android:attr/textColor"
-        android:src="@drawable/ic_cast_connected"
-        android:forceHasOverlappingRendering="false" />
-
     <!-- Seek Bar -->
     <!-- As per Material Design on Biderectionality, this is forced to LTR in code -->
     <SeekBar
diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
index 5b9ca1b..966f992 100644
--- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml
+++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
@@ -26,17 +26,38 @@
     android:focusable="true"
     android:theme="@style/Theme.SystemUI.QuickSettings.Header">
 
-    <com.android.systemui.statusbar.policy.Clock
-        android:id="@+id/clock"
+    <LinearLayout
+        android:id="@+id/clock_container"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:minWidth="48dp"
-        android:minHeight="48dp"
+        android:orientation="horizontal"
+        android:layout_gravity="center_vertical|start"
         android:gravity="center_vertical|start"
-        android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
-        android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
-        android:singleLine="true"
-        android:textAppearance="@style/TextAppearance.QS.Status" />
+        >
+
+        <com.android.systemui.statusbar.policy.Clock
+            android:id="@+id/clock"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:minWidth="48dp"
+            android:minHeight="48dp"
+            android:gravity="center_vertical|start"
+            android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
+            android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
+            android:singleLine="true"
+            android:textAppearance="@style/TextAppearance.QS.Status" />
+
+        <com.android.systemui.statusbar.policy.VariableDateView
+            android:id="@+id/date_clock"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:gravity="center_vertical|start"
+            android:singleLine="true"
+            android:textAppearance="@style/TextAppearance.QS.Status"
+            systemui:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
+            systemui:shortDatePattern="@string/abbrev_month_day_no_year"
+        />
+    </LinearLayout>
 
     <include layout="@layout/qs_carrier_group"
         android:id="@+id/carrier_group"
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
index bff93a9..b1e8c38 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
@@ -36,7 +36,7 @@
         android:layout_weight="1"
         android:gravity="center_vertical|start" >
 
-        <com.android.systemui.statusbar.policy.DateView
+        <com.android.systemui.statusbar.policy.VariableDateView
             android:id="@+id/date"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
@@ -44,9 +44,16 @@
             android:gravity="center_vertical"
             android:singleLine="true"
             android:textAppearance="@style/TextAppearance.QS.Status"
-            systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
+            systemui:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
+            systemui:shortDatePattern="@string/abbrev_month_day_no_year"
+        />
     </FrameLayout>
 
+    <!-- We want this to be centered (to align with notches). In order to do that, the following
+         has to hold (in portrait):
+         * date_container and privacy_container must have the same width and weight
+         * header_text_container must be gone
+         -->
     <android.widget.Space
         android:id="@+id/space"
         android:layout_width="0dp"
@@ -73,7 +80,7 @@
         android:layout_weight="1"
         android:gravity="center_vertical|end" >
 
-    <include layout="@layout/ongoing_privacy_chip" />
+        <include layout="@layout/ongoing_privacy_chip" />
 
     </FrameLayout>
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index d1cc01f..c122829 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -113,6 +113,8 @@
                         android:layout_height="wrap_content"
                         android:minHeight="48dp"
                         android:layout_weight="1"
+                        android:layout_gravity="fill_vertical"
+                        android:gravity="center_vertical"
                         android:text="@string/screenrecord_taps_label"
                         android:textAppearance="?android:attr/textAppearanceMedium"
                         android:fontFamily="@*android:string/config_headlineFontFamily"
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index bea50e8..71e8fc9 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -62,8 +62,7 @@
     <com.android.systemui.statusbar.LightRevealScrim
             android:id="@+id/light_reveal_scrim"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:visibility="gone" />
+            android:layout_height="match_parent" />
 
     <include layout="@layout/status_bar_expanded"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index da80b85..0a34dfd 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -95,4 +95,7 @@
     <dimen name="controls_top_margin">24dp</dimen>
 
     <dimen name="global_actions_grid_item_layout_height">80dp</dimen>
+
+    <!-- Internet panel related dimensions -->
+    <dimen name="internet_dialog_list_max_width">624dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index b5337d3..3121ce3 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -69,6 +69,10 @@
     <declare-styleable name="DateView">
         <attr name="datePattern" format="string" />
     </declare-styleable>
+    <declare-styleable name="VariableDateView">
+        <attr name="longDatePattern" format="string" />
+        <attr name="shortDatePattern" format="string" />
+    </declare-styleable>
     <declare-styleable name="PseudoGridView">
         <attr name="numColumns" format="integer" />
         <attr name="verticalSpacing" format="dimension" />
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 2260d21..f539b0c 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 -->
-<resources>
+<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
     <drawable name="notification_number_text_color">#ffffffff</drawable>
     <drawable name="ticker_background_color">#ff1d1d1d</drawable>
     <drawable name="system_bar_background">@color/system_bar_background_opaque</drawable>
@@ -187,10 +187,8 @@
     <!-- UDFPS colors -->
     <color name="udfps_enroll_icon">#000000</color>                         <!-- 100% black -->
     <color name="udfps_moving_target_fill">#cc4285f4</color>                <!-- 80% blue -->
-    <color name="udfps_enroll_progress">#ff669DF6</color>                   <!-- 100% blue -->
-
-    <!-- Logout button -->
-    <color name="logout_button_bg_color">#ccffffff</color>
+    <color name="udfps_enroll_progress">#ff669DF6</color>                   <!-- blue 400 -->
+    <color name="udfps_enroll_progress_help">#ffEE675C</color>              <!-- red 400 -->
 
     <!-- Color for the Assistant invocation lights -->
     <color name="default_invocation_lights_color">#ffffffff</color>         <!-- white -->
@@ -281,4 +279,16 @@
     <color name="wallet_card_border">#33FFFFFF</color>
 
     <color name="people_tile_background">@android:color/system_accent2_50</color>
+
+    <!-- Internet Dialog -->
+    <!-- Material next state on color-->
+    <color name="settingslib_state_on_color">@color/settingslib_state_on</color>
+    <!-- Material next state off color-->
+    <color name="settingslib_state_off_color">@color/settingslib_state_off</color>
+    <!-- Material next track on color-->
+    <color name="settingslib_track_on_color">@color/settingslib_track_on</color>
+    <!-- Material next track off color-->
+    <color name="settingslib_track_off_color">@color/settingslib_track_off</color>
+    <color name="connected_network_primary_color">#191C18</color>
+    <color name="connected_network_secondary_color">#41493D</color>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 78db2a8..f76f7d8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -331,11 +331,10 @@
     <dimen name="global_screenshot_x_scale">80dp</dimen>
     <dimen name="screenshot_bg_protection_height">242dp</dimen>
     <dimen name="screenshot_preview_elevation">4dp</dimen>
-    <dimen name="screenshot_offset_y">24dp</dimen>
+    <dimen name="screenshot_offset_y">8dp</dimen>
     <dimen name="screenshot_offset_x">16dp</dimen>
     <dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen>
     <dimen name="screenshot_dismiss_button_margin">8dp</dimen>
-    <dimen name="screenshot_action_container_offset_y">16dp</dimen>
     <dimen name="screenshot_action_container_corner_radius">18dp</dimen>
     <dimen name="screenshot_action_container_padding_vertical">4dp</dimen>
     <dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
@@ -1243,10 +1242,7 @@
     <integer name="wired_charging_keyguard_text_animation_distance">-30</integer>
 
     <!-- Logout button -->
-    <dimen name="logout_button_layout_height">32dp</dimen>
-    <dimen name="logout_button_padding_horizontal">16dp</dimen>
-    <dimen name="logout_button_margin_bottom">12dp</dimen>
-    <dimen name="logout_button_corner_radius">4dp</dimen>
+    <dimen name="logout_button_corner_radius">50dp</dimen>
 
     <!--  Blur radius on status bar window and power menu  -->
     <dimen name="min_window_blur_radius">1px</dimen>
@@ -1299,8 +1295,6 @@
     <dimen name="qs_media_action_margin">12dp</dimen>
     <dimen name="qs_seamless_height">24dp</dimen>
     <dimen name="qs_seamless_icon_size">12dp</dimen>
-    <dimen name="qs_seamless_fallback_icon_size">@dimen/qs_seamless_icon_size</dimen>
-    <dimen name="qs_seamless_fallback_margin">20dp</dimen>
     <dimen name="qs_footer_horizontal_margin">22dp</dimen>
     <dimen name="qs_media_disabled_seekbar_height">1dp</dimen>
     <dimen name="qs_media_enabled_seekbar_height">2dp</dimen>
@@ -1590,4 +1584,42 @@
     <!-- The padding between the icon and the text. -->
     <dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen>
     <dimen name="ongoing_call_chip_corner_radius">28dp</dimen>
+
+    <!-- Internet panel related dimensions -->
+    <dimen name="internet_dialog_list_margin">12dp</dimen>
+    <dimen name="internet_dialog_list_max_height">646dp</dimen>
+    <dimen name="internet_dialog_list_max_width">@dimen/match_parent</dimen>
+
+    <!-- Signal icon in internet dialog -->
+    <dimen name="signal_strength_icon_size">24dp</dimen>
+
+    <!-- Internet dialog related dimensions -->
+    <dimen name="internet_dialog_corner_radius">24dp</dimen>
+    <!-- End margin of network layout -->
+    <dimen name="internet_dialog_network_layout_margin">16dp</dimen>
+    <!-- Size of switch bar in internet dialog -->
+    <dimen name="settingslib_switchbar_margin">16dp</dimen>
+    <!-- Minimum width of switch -->
+    <dimen name="settingslib_min_switch_width">52dp</dimen>
+    <!-- Size of layout margin left -->
+    <dimen name="settingslib_switchbar_padding_left">20dp</dimen>
+    <!-- Size of layout margin right -->
+    <dimen name="settingslib_switchbar_padding_right">20dp</dimen>
+    <!-- Radius of switch bar -->
+    <dimen name="settingslib_switch_bar_radius">35dp</dimen>
+    <!-- Margin of switch thumb -->
+    <dimen name="settingslib_switch_thumb_margin">4dp</dimen>
+    <!-- Size of switch thumb -->
+    <dimen name="settingslib_switch_thumb_size">20dp</dimen>
+    <!-- Width of switch track -->
+    <dimen name="settingslib_switch_track_width">52dp</dimen>
+    <!-- Height of switch track -->
+    <dimen name="settingslib_switch_track_height">28dp</dimen>
+    <!-- Radius of switch track -->
+    <dimen name="settingslib_switch_track_radius">35dp</dimen>
+
+    <!-- Height percentage of the parent container occupied by the communal view -->
+    <item name="communal_source_height_percentage" format="float" type="dimen">0.80</item>
+
+    <dimen name="drag_and_drop_icon_size">70dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a670216..ebf6e48 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -211,6 +211,8 @@
 
     <!-- Power menu item for taking a screenshot [CHAR LIMIT=20]-->
     <string name="global_action_screenshot">Screenshot</string>
+    <!-- Message shown in power menu when smart lock has been disabled [CHAR_LIMIT=NONE] -->
+    <string name="global_action_smart_lock_disabled">Smart Lock disabled</string>
 
     <!-- text to show in place of RemoteInput images when they cannot be shown.
          [CHAR LIMIT=50] -->
@@ -1006,7 +1008,7 @@
     <string name="sensor_privacy_start_use_mic_camera_dialog_content">This unblocks access for all apps and services allowed to use your camera or microphone.</string>
 
     <!-- Default name for the media device shown in the output switcher when the name is not available [CHAR LIMIT=30] -->
-    <string name="media_seamless_remote_device">Device</string>
+    <string name="media_seamless_other_device">Other device</string>
 
     <!-- QuickStep: Accessibility to toggle overview [CHAR LIMIT=40] -->
     <string name="quick_step_accessibility_toggle_overview">Toggle Overview</string>
@@ -2992,4 +2994,37 @@
     <string name="global_actions_change_description" translatable="false"><xliff:g>%1$s</xliff:g></string>
     <!-- URL for more information about changes in global actions -->
     <string name="global_actions_change_url" translatable="false"></string>
+
+    <!-- Provider Model: Default title of the mobile network in the mobile layout. [CHAR LIMIT=50] -->
+    <string name="mobile_data_settings_title">Mobile data</string>
+    <!-- Provider Model: Summary text separator for preferences including a short description
+         (eg. "Connected / 5G"). [CHAR LIMIT=50] -->
+    <string name="preference_summary_default_combination"><xliff:g id="state" example="Connected">%1$s</xliff:g> / <xliff:g id="networkMode" example="LTE">%2$s</xliff:g></string>
+    <!-- Provider Model:
+         Summary indicating that a SIM has an active mobile data connection [CHAR LIMIT=50] -->
+    <string name="mobile_data_connection_active">Connected</string>
+    <!-- Provider Model:
+     Summary indicating that a SIM has no mobile data connection [CHAR LIMIT=50] -->
+    <string name="mobile_data_off_summary">Mobile data won\u0027t auto\u2011connect</string>
+    <!-- Provider Model:
+     Summary indicating that a active SIM and no network available [CHAR LIMIT=50] -->
+    <string name="mobile_data_no_connection">No connection</string>
+    <!-- Provider Model: Summary indicating that no other networks available [CHAR LIMIT=50] -->
+    <string name="non_carrier_network_unavailable">No other networks available</string>
+    <!-- Provider Model: Summary indicating that no networks available [CHAR LIMIT=50] -->
+    <string name="all_network_unavailable">No networks available</string>
+    <!-- Provider Model: Panel title text for turning on the Wi-Fi networks. [CHAR LIMIT=40] -->
+    <string name="turn_on_wifi">Wi\u2011Fi</string>
+    <!-- Provider Model: Title for detail page of wifi network [CHAR LIMIT=30] -->
+    <string name="pref_title_network_details" msgid="7329759534269363308">"Network details"</string>
+    <!-- Provider Model: Panel subtitle for tapping a network to connect to internet. [CHAR LIMIT=60] -->
+    <string name="tap_a_network_to_connect">Tap a network to connect</string>
+    <!-- Provider Model: Panel subtitle for unlocking screen to view networks. [CHAR LIMIT=60] -->
+    <string name="unlock_to_view_networks">Unlock to view networks</string>
+    <!-- Provider Model: Wi-Fi settings. text displayed when Wi-Fi is on and network list is empty [CHAR LIMIT=50]-->
+    <string name="wifi_empty_list_wifi_on">Searching for networks\u2026</string>
+    <!-- Provider Model: Failure notification for connect -->
+    <string name="wifi_failed_connect_message">Failed to connect to network</string>
+    <!-- Provider Model: Title to see all the networks [CHAR LIMIT=50] -->
+    <string name="see_all_networks">See all</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 51eabf6..d254742 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -903,4 +903,52 @@
       <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen.  -->
       <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
     </style>
+
+    <style name="Animation.InternetDialog" parent="@android:style/Animation.InputMethod">
+    </style>
+
+    <style name="Widget.SliceView.Panel">
+        <item name="titleSize">16sp</item>
+        <item name="rowStyle">@style/SliceRow</item>
+        <item name="android:background">?android:attr/colorBackgroundFloating</item>
+    </style>
+
+    <style name="SliceRow">
+        <!-- 2dp start padding for the start icon -->
+        <item name="titleItemStartPadding">2dp</item>
+        <item name="titleItemEndPadding">0dp</item>
+
+        <!-- Padding between content and the start icon is 14dp -->
+        <item name="contentStartPadding">14dp</item>
+        <!-- Padding between content and end items is 16dp -->
+        <item name="contentEndPadding">16dp</item>
+
+        <!-- Both side margins of end item are 16dp -->
+        <item name="endItemStartPadding">16dp</item>
+        <item name="endItemEndPadding">16dp</item>
+
+        <!-- Both side margins of bottom divider are 12dp -->
+        <item name="bottomDividerStartPadding">12dp</item>
+        <item name="bottomDividerEndPadding">12dp</item>
+
+        <item name="actionDividerHeight">32dp</item>
+    </style>
+
+    <style name="Theme.SystemUI.Dialog.Internet">
+        <item name="android:windowBackground">@drawable/internet_dialog_background</item>
+    </style>
+
+    <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:switchMinWidth">@dimen/settingslib_min_switch_width</item>
+    </style>
+
+    <style name="TrimmedHorizontalProgressBar"
+           parent="android:Widget.Material.ProgressBar.Horizontal">
+        <item name="android:indeterminateDrawable">
+            @drawable/progress_indeterminate_horizontal_material_trimmed
+        </item>
+        <item name="android:minHeight">4dp</item>
+        <item name="android:maxHeight">4dp</item>
+    </style>
+
 </resources>
diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml
index d6c6a60..c3510b6 100644
--- a/packages/SystemUI/res/xml/media_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_collapsed.xml
@@ -44,23 +44,6 @@
         />
 
     <Constraint
-        android:id="@+id/media_seamless_fallback"
-        android:layout_width="@dimen/qs_seamless_fallback_icon_size"
-        android:layout_height="@dimen/qs_seamless_fallback_icon_size"
-        android:layout_marginTop="@dimen/qs_media_padding"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        android:layout_marginStart="@dimen/qs_center_guideline_padding"
-        android:layout_marginEnd="@dimen/qs_seamless_fallback_margin"
-        android:alpha="0.5"
-        android:visibility="gone"
-        app:layout_constraintHorizontal_bias="1"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/center_horizontal_guideline"
-        app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
-        app:layout_constraintEnd_toEndOf="parent"
-        />
-
-    <Constraint
         android:id="@+id/album_art"
         android:layout_width="@dimen/qs_media_album_size_small"
         android:layout_height="@dimen/qs_media_album_size_small"
diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml
index 0e284e6..6b83aae 100644
--- a/packages/SystemUI/res/xml/media_expanded.xml
+++ b/packages/SystemUI/res/xml/media_expanded.xml
@@ -45,22 +45,6 @@
         android:layout_marginBottom="4dp" />
 
     <Constraint
-        android:id="@+id/media_seamless_fallback"
-        android:layout_width="@dimen/qs_seamless_fallback_icon_size"
-        android:layout_height="@dimen/qs_seamless_fallback_icon_size"
-        android:layout_marginTop="@dimen/qs_media_padding"
-        android:layout_marginBottom="16dp"
-        android:layout_marginStart="@dimen/qs_center_guideline_padding"
-        android:layout_marginEnd="@dimen/qs_seamless_fallback_margin"
-        android:alpha="0.5"
-        android:visibility="gone"
-        app:layout_constraintHorizontal_bias="1"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
-        app:layout_constraintEnd_toEndOf="parent"
-        />
-
-    <Constraint
         android:id="@+id/album_art"
         android:layout_width="@dimen/qs_media_album_size"
         android:layout_height="@dimen/qs_media_album_size"
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 28a54d5..e115c34 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -132,8 +132,7 @@
                         .alpha(1f)
                         .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
                         .start();
-            } else if (mUnlockedScreenOffAnimationController
-                        .isScreenOffLightRevealAnimationPlaying()) {
+            } else if (mUnlockedScreenOffAnimationController.shouldAnimateInKeyguard()) {
                 mKeyguardViewVisibilityAnimating = true;
 
                 // Ask the screen off animation controller to animate the keyguard visibility for us
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index edb05691..dd3bb89 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -27,6 +27,7 @@
 import android.widget.ImageView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.settingslib.Utils;
 import com.android.systemui.Dumpable;
@@ -79,7 +80,11 @@
         mLockIcon.setImageDrawable(drawable);
     }
 
-    void setCenterLocation(@NonNull PointF center, int radius) {
+    /**
+     * Set the location of the lock icon.
+     */
+    @VisibleForTesting
+    public void setCenterLocation(@NonNull PointF center, int radius) {
         mLockIconCenter = center;
         mRadius = radius;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 509ac8a..2a4022c 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -46,6 +46,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.AuthRippleController;
 import com.android.systemui.biometrics.UdfpsController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
@@ -67,7 +68,8 @@
 /**
  * Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen.
  *
- * This view will only be shown if the user has UDFPS or FaceAuth enrolled
+ * For devices with UDFPS, the lock icon will show at the sensor location. Else, the lock
+ * icon will show a set distance from the bottom of the device.
  */
 @StatusBarComponent.StatusBarScope
 public class LockIconViewController extends ViewController<LockIconView> implements Dumpable {
@@ -99,6 +101,7 @@
     @NonNull private CharSequence mUnlockedLabel;
     @NonNull private CharSequence mLockedLabel;
     @Nullable private final Vibrator mVibrator;
+    @Nullable private final AuthRippleController mAuthRippleController;
 
     private boolean mIsDozing;
     private boolean mIsBouncerShowing;
@@ -135,7 +138,8 @@
             @NonNull AccessibilityManager accessibilityManager,
             @NonNull ConfigurationController configurationController,
             @NonNull @Main DelayableExecutor executor,
-            @Nullable Vibrator vibrator
+            @Nullable Vibrator vibrator,
+            @Nullable AuthRippleController authRippleController
     ) {
         super(view);
         mStatusBarStateController = statusBarStateController;
@@ -148,6 +152,7 @@
         mConfigurationController = configurationController;
         mExecutor = executor;
         mVibrator = vibrator;
+        mAuthRippleController = authRippleController;
 
         final Context context = view.getContext();
         mUnlockIcon = mView.getContext().getResources().getDrawable(
@@ -168,15 +173,14 @@
 
     @Override
     protected void onInit() {
+        mAuthController.addCallback(mAuthControllerCallback);
+        mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
+
         mView.setAccessibilityDelegate(mAccessibilityDelegate);
     }
 
     @Override
     protected void onViewAttached() {
-        // we check this here instead of onInit since the FingerprintManager + FaceManager may not
-        // have started up yet onInit
-        mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
-
         updateConfiguration();
         updateKeyguardShowing();
         mUserUnlockedWithBiometric = false;
@@ -497,8 +501,10 @@
                     if (!wasClickableOnDownEvent()) {
                         return;
                     }
+                    mDetectedLongPress = true;
 
-                    if (mVibrator != null) {
+                    if (onAffordanceClick() && mVibrator != null) {
+                        // only vibrate if the click went through and wasn't intercepted by falsing
                         mVibrator.vibrate(
                                 Process.myUid(),
                                 getContext().getOpPackageName(),
@@ -506,8 +512,6 @@
                                 "lockIcon-onLongPress",
                                 VIBRATION_SONIFICATION_ATTRIBUTES);
                     }
-                    mDetectedLongPress = true;
-                    onAffordanceClick();
                 }
 
                 public boolean onSingleTapUp(MotionEvent e) {
@@ -531,15 +535,24 @@
                     return mDownDetected;
                 }
 
-                private void onAffordanceClick() {
+                /**
+                 * Whether we tried to launch the affordance.
+                 *
+                 * If falsing intercepts the click, returns false.
+                 */
+                private boolean onAffordanceClick() {
                     if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
-                        return;
+                        return false;
                     }
 
                     // pre-emptively set to true to hide view
                     mIsBouncerShowing = true;
+                    if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
+                        mAuthRippleController.showRipple(FINGERPRINT);
+                    }
                     updateVisibility();
                     mKeyguardViewController.showBouncer(/* scrim */ true);
+                    return true;
                 }
             });
 
@@ -577,4 +590,12 @@
     public void setAlpha(float alpha) {
         mView.setAlpha(alpha);
     }
+
+    private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+        @Override
+        public void onAllAuthenticatorsRegistered() {
+            mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
+            updateConfiguration();
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 76f30a8..00b33a4 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -65,6 +65,7 @@
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.privacy.PrivacyItemController;
 import com.android.systemui.qs.ReduceBrightColorsController;
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.screenrecord.RecordingController;
@@ -364,6 +365,7 @@
     @Inject Lazy<UiEventLogger> mUiEventLogger;
     @Inject Lazy<FeatureFlags> mFeatureFlagsLazy;
     @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy;
+    @Inject Lazy<InternetDialogFactory> mInternetDialogFactory;
 
     @Inject
     public Dependency() {
@@ -578,6 +580,7 @@
         mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get);
         mProviders.put(EdgeBackGestureHandler.Factory.class,
                 mEdgeBackGestureHandlerFactoryLazy::get);
+        mProviders.put(InternetDialogFactory.class, mInternetDialogFactory::get);
         mProviders.put(UiEventLogger.class, mUiEventLogger::get);
         mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
         mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get);
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index a68f796..8379ccf 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -20,6 +20,8 @@
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.SystemClock;
@@ -29,7 +31,6 @@
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Size;
-import android.view.DisplayInfo;
 import android.view.SurfaceHolder;
 import android.view.WindowManager;
 
@@ -90,7 +91,7 @@
         mMiniBitmap = null;
     }
 
-    class GLEngine extends Engine {
+    class GLEngine extends Engine implements DisplayListener {
         // Surface is rejected if size below a threshold on some devices (ie. 8px on elfin)
         // set min to 64 px (CTS covers this), please refer to ag/4867989 for detail.
         @VisibleForTesting
@@ -102,15 +103,15 @@
         private EglHelper mEglHelper;
         private final Runnable mFinishRenderingTask = this::finishRendering;
         private boolean mNeedRedraw;
-        private int mWidth = 1;
-        private int mHeight = 1;
+
+        private boolean mDisplaySizeValid = false;
+        private int mDisplayWidth = 1;
+        private int mDisplayHeight = 1;
+
         private int mImgWidth = 1;
         private int mImgHeight = 1;
-        private float mPageWidth = 1.f;
-        private float mPageOffset = 1.f;
 
-        GLEngine() {
-        }
+        GLEngine() { }
 
         @VisibleForTesting
         GLEngine(Handler handler) {
@@ -119,18 +120,29 @@
 
         @Override
         public void onCreate(SurfaceHolder surfaceHolder) {
+            Trace.beginSection("ImageWallpaper.Engine#onCreate");
             mEglHelper = getEglHelperInstance();
             // Deferred init renderer because we need to get wallpaper by display context.
             mRenderer = getRendererInstance();
             setFixedSizeAllowed(true);
             updateSurfaceSize();
-            Rect window = getDisplayContext()
-                    .getSystemService(WindowManager.class)
-                    .getCurrentWindowMetrics()
-                    .getBounds();
-            mHeight = window.height();
-            mWidth = window.width();
             mRenderer.setOnBitmapChanged(this::updateMiniBitmap);
+            getDisplayContext().getSystemService(DisplayManager.class)
+                    .registerDisplayListener(this, mWorker.getThreadHandler());
+            Trace.endSection();
+        }
+
+        @Override
+        public void onDisplayAdded(int displayId) { }
+
+        @Override
+        public void onDisplayRemoved(int displayId) { }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            if (displayId == getDisplayContext().getDisplayId()) {
+                mDisplaySizeValid = false;
+            }
         }
 
         EglHelper getEglHelperInstance() {
@@ -154,26 +166,10 @@
             if (pages == mPages) return;
             mPages = pages;
             if (mMiniBitmap == null || mMiniBitmap.isRecycled()) return;
-            updateShift();
             mWorker.getThreadHandler().post(() ->
                     computeAndNotifyLocalColors(new ArrayList<>(mColorAreas), mMiniBitmap));
         }
 
-        private void updateShift() {
-            if (mImgHeight == 0) {
-                mPageOffset = 0;
-                mPageWidth = 1;
-                return;
-            }
-            // calculate shift
-            DisplayInfo displayInfo = new DisplayInfo();
-            getDisplayContext().getDisplay().getDisplayInfo(displayInfo);
-            int screenWidth = displayInfo.getNaturalWidth();
-            float imgWidth = Math.min(mImgWidth > 0 ? screenWidth / (float) mImgWidth : 1.f, 1.f);
-            mPageWidth = imgWidth;
-            mPageOffset = (1 - imgWidth) / (float) (mPages - 1);
-        }
-
         private void updateMiniBitmap(Bitmap b) {
             if (b == null) return;
             int size = Math.min(b.getWidth(), b.getHeight());
@@ -203,13 +199,22 @@
         }
 
         @Override
+        public boolean shouldWaitForEngineShown() {
+            return true;
+        }
+
+        @Override
         public void onDestroy() {
+            getDisplayContext().getSystemService(DisplayManager.class)
+                    .unregisterDisplayListener(this);
             mMiniBitmap = null;
             mWorker.getThreadHandler().post(() -> {
+                Trace.beginSection("ImageWallpaper.Engine#onDestroy");
                 mRenderer.finish();
                 mRenderer = null;
                 mEglHelper.finish();
                 mEglHelper = null;
+                Trace.endSection();
             });
         }
 
@@ -268,6 +273,16 @@
          * (1-Wr)].
          */
         private RectF pageToImgRect(RectF area) {
+            if (!mDisplaySizeValid) {
+                Rect window = getDisplayContext()
+                        .getSystemService(WindowManager.class)
+                        .getCurrentWindowMetrics()
+                        .getBounds();
+                mDisplayWidth = window.width();
+                mDisplayHeight = window.height();
+                mDisplaySizeValid = true;
+            }
+
             // Width of a page for the caller of this API.
             float virtualPageWidth = 1f / (float) mPages;
             float leftPosOnPage = (area.left % virtualPageWidth) / virtualPageWidth;
@@ -275,12 +290,24 @@
             int currentPage = (int) Math.floor(area.centerX() / virtualPageWidth);
 
             RectF imgArea = new RectF();
+
+            if (mImgWidth == 0 || mImgHeight == 0 || mDisplayWidth <= 0 || mDisplayHeight <= 0) {
+                return imgArea;
+            }
+
             imgArea.bottom = area.bottom;
             imgArea.top = area.top;
+
+            float imageScale = Math.min(((float) mImgHeight) / mDisplayHeight, 1);
+            float mappedScreenWidth = mDisplayWidth * imageScale;
+            float pageWidth = Math.min(1.0f,
+                    mImgWidth > 0 ? mappedScreenWidth / (float) mImgWidth : 1.f);
+            float pageOffset = (1 - pageWidth) / (float) (mPages - 1);
+
             imgArea.left = MathUtils.constrain(
-                    leftPosOnPage * mPageWidth + currentPage * mPageOffset, 0, 1);
+                    leftPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
             imgArea.right = MathUtils.constrain(
-                    rightPosOnPage * mPageWidth + currentPage * mPageOffset, 0, 1);
+                    rightPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
             if (imgArea.left > imgArea.right) {
                 // take full page
                 imgArea.left = 0;
@@ -293,7 +320,6 @@
         private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas,
                 Bitmap b) {
             List<WallpaperColors> colors = new ArrayList<>(areas.size());
-            updateShift();
             for (int i = 0; i < areas.size(); i++) {
                 RectF area = pageToImgRect(areas.get(i));
                 if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) {
@@ -322,8 +348,10 @@
         public void onSurfaceCreated(SurfaceHolder holder) {
             if (mWorker == null) return;
             mWorker.getThreadHandler().post(() -> {
+                Trace.beginSection("ImageWallpaper#onSurfaceCreated");
                 mEglHelper.init(holder, needSupportWideColorGamut());
                 mRenderer.onSurfaceCreated();
+                Trace.endSection();
             });
         }
 
@@ -340,9 +368,11 @@
         }
 
         private void drawFrame() {
+            Trace.beginSection("ImageWallpaper#drawFrame");
             preRender();
             requestRender();
             postRender();
+            Trace.endSection();
         }
 
         public void preRender() {
@@ -409,6 +439,7 @@
             // This method should only be invoked from worker thread.
             Trace.beginSection("ImageWallpaper#postRender");
             scheduleFinishRendering();
+            reportEngineShown(false /* waitForEngineShown */);
             Trace.endSection();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index affad7a..8c63f7b 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -438,6 +438,12 @@
             private boolean mCancelled;
 
             @Override
+            public void onAnimationStart(Animator animation) {
+                super.onAnimationStart(animation);
+                mCallback.onBeginDrag(animView);
+            }
+
+            @Override
             public void onAnimationCancel(Animator animation) {
                 mCancelled = true;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
index 81a13a2..4082015 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
@@ -57,7 +57,9 @@
         public void run() {
             mView.removeCallbacks(this);
             mView.show(false /* show */, true /* animate */, () -> {
-                mWindowManager.removeView(mView);
+                if (mView.isAttachedToWindow()) {
+                    mWindowManager.removeView(mView);
+                }
             });
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index c9e6771..5616a00 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -40,10 +40,10 @@
  * After restoring is done, a [ACTION_RESTORE_FINISHED] intent will be send to SystemUI user 0,
  * indicating that restoring is finished for a given user.
  */
-class BackupHelper : BackupAgentHelper() {
+open class BackupHelper : BackupAgentHelper() {
 
     companion object {
-        private const val TAG = "BackupHelper"
+        const val TAG = "BackupHelper"
         internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME
         private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite"
         private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 3f61d3c..fd37b35 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -126,6 +126,7 @@
         boolean mCredentialAllowed;
         boolean mSkipIntro;
         long mOperationId;
+        long mRequestId;
         @BiometricMultiSensorMode int mMultiSensorConfig;
     }
 
@@ -172,6 +173,12 @@
             return this;
         }
 
+        /** Unique id for this request. */
+        public Builder setRequestId(long requestId) {
+            mConfig.mRequestId = requestId;
+            return this;
+        }
+
         /** The multi-sensor mode. */
         public Builder setMultiSensorConfig(@BiometricMultiSensorMode int multiSensorConfig) {
             mConfig.mMultiSensorConfig = multiSensorConfig;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 0ce1846..0790af9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -42,6 +42,7 @@
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintStateListener;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.IUdfpsHbmListener;
 import android.os.Bundle;
@@ -49,6 +50,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.SparseBooleanArray;
 import android.view.MotionEvent;
 import android.view.WindowManager;
 
@@ -76,6 +78,9 @@
 /**
  * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
  * appropriate biometric UI (e.g. BiometricDialogView).
+ *
+ * Also coordinates biometric-related things, such as UDFPS, with
+ * {@link com.android.keyguard.KeyguardUpdateMonitor}
  */
 @SysUISingleton
 public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@@ -115,6 +120,8 @@
     @Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps;
     @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
 
+    @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
+
     private class BiometricTaskStackListener extends TaskStackListener {
         @Override
         public void onTaskStackChanged() {
@@ -122,6 +129,21 @@
         }
     }
 
+    private final FingerprintStateListener mFingerprintStateListener =
+            new FingerprintStateListener() {
+        @Override
+        public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+            Log.d(TAG, "onEnrollmentsChanged, userId: " + userId
+                    + ", sensorId: " + sensorId
+                    + ", hasEnrollments: " + hasEnrollments);
+            for (FingerprintSensorPropertiesInternal prop : mUdfpsProps) {
+                if (prop.sensorId == sensorId) {
+                    mUdfpsEnrolledForUser.put(userId, hasEnrollments);
+                }
+            }
+        }
+    };
+
     @NonNull
     private final IFingerprintAuthenticatorsRegisteredCallback
             mFingerprintAuthenticatorsRegisteredCallback =
@@ -436,6 +458,7 @@
         mUdfpsControllerFactory = udfpsControllerFactory;
         mSidefpsControllerFactory = sidefpsControllerFactory;
         mWindowManager = windowManager;
+        mUdfpsEnrolledForUser = new SparseBooleanArray();
         mOrientationListener = new BiometricOrientationEventListener(context,
                 () -> {
                     onOrientationChanged();
@@ -474,6 +497,7 @@
         if (mFingerprintManager != null) {
             mFingerprintManager.addAuthenticatorsRegisteredCallback(
                     mFingerprintAuthenticatorsRegisteredCallback);
+            mFingerprintManager.registerFingerprintStateListener(mFingerprintStateListener);
         }
 
         mTaskStackListener = new BiometricTaskStackListener();
@@ -501,7 +525,7 @@
     @Override
     public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
             int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
-            int userId, String opPackageName, long operationId,
+            int userId, long operationId, String opPackageName, long requestId,
             @BiometricMultiSensorMode int multiSensorConfig) {
         @Authenticators.Types final int authenticators = promptInfo.getAuthenticators();
 
@@ -515,6 +539,7 @@
                     + ", credentialAllowed: " + credentialAllowed
                     + ", requireConfirmation: " + requireConfirmation
                     + ", operationId: " + operationId
+                    + ", requestId: " + requestId
                     + ", multiSensorConfig: " + multiSensorConfig);
         }
         SomeArgs args = SomeArgs.obtain();
@@ -526,6 +551,7 @@
         args.argi1 = userId;
         args.arg6 = opPackageName;
         args.arg7 = operationId;
+        args.arg8 = requestId;
         args.argi2 = multiSensorConfig;
 
         boolean skipAnimation = false;
@@ -629,6 +655,7 @@
         if (mCurrentDialog == null) {
             // Could be possible if the caller canceled authentication after credential success
             // but before the client was notified.
+            if (DEBUG) Log.d(TAG, "dialog already gone");
             return;
         }
 
@@ -670,7 +697,7 @@
             return false;
         }
 
-        return mFingerprintManager.hasEnrolledTemplatesForAnySensor(userId, mUdfpsProps);
+        return mUdfpsEnrolledForUser.get(userId);
     }
 
     private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
@@ -683,6 +710,7 @@
         final int userId = args.argi1;
         final String opPackageName = (String) args.arg6;
         final long operationId = (long) args.arg7;
+        final long requestId = (long) args.arg8;
         final @BiometricMultiSensorMode int multiSensorConfig = args.argi2;
 
         // Create a new dialog but do not replace the current one yet.
@@ -695,6 +723,7 @@
                 opPackageName,
                 skipAnimation,
                 operationId,
+                requestId,
                 multiSensorConfig);
 
         if (newDialog == null) {
@@ -772,7 +801,7 @@
 
     protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation,
             int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName,
-            boolean skipIntro, long operationId,
+            boolean skipIntro, long operationId, long requestId,
             @BiometricMultiSensorMode int multiSensorConfig) {
         return new AuthContainerView.Builder(mContext)
                 .setCallback(this)
@@ -782,11 +811,16 @@
                 .setOpPackageName(opPackageName)
                 .setSkipIntro(skipIntro)
                 .setOperationId(operationId)
+                .setRequestId(requestId)
                 .setMultiSensorConfig(multiSensorConfig)
                 .build(sensorIds, credentialAllowed, mFpProps, mFaceProps);
     }
 
-    interface Callback {
+    /**
+     * AuthController callback used to receive signal for when biometric authenticators are
+     * registered.
+     */
+    public interface Callback {
         /**
          * Called when authenticators are registered. If authenticators are already
          * registered before this call, this callback will never be triggered.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 53fd9a3..dac6f9cc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -43,7 +43,6 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.Trace;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
@@ -76,6 +75,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
+import com.android.systemui.util.time.SystemClock;
 
 import java.util.HashSet;
 import java.util.Optional;
@@ -127,6 +127,7 @@
     @Nullable private final UdfpsHbmProvider mHbmProvider;
     @NonNull private final KeyguardBypassController mKeyguardBypassController;
     @NonNull private final ConfigurationController mConfigurationController;
+    @NonNull private final SystemClock mSystemClock;
     @VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener;
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
     // sensors, this, in addition to a lot of the code here, will be updated.
@@ -165,7 +166,8 @@
     public static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES =
             new AudioAttributes.Builder()
                     .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                    .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+                    // vibration will bypass battery saver mode:
+                    .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY)
                     .build();
 
     public static final VibrationEffect EFFECT_CLICK =
@@ -213,14 +215,12 @@
         }
 
         void onAcquiredGood() {
-            Log.d(TAG, "onAcquiredGood");
             if (mEnrollHelper != null) {
                 mEnrollHelper.animateIfLastStep();
             }
         }
 
         void onEnrollmentHelp() {
-            Log.d(TAG, "onEnrollmentHelp");
             if (mEnrollHelper != null) {
                 mEnrollHelper.onEnrollmentHelp();
             }
@@ -454,19 +454,19 @@
                         final String touchInfo = String.format(
                                 "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
                                 minor, major, v, exceedsVelocityThreshold);
-                        final long sinceLastLog = SystemClock.elapsedRealtime() - mTouchLogTime;
+                        final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime;
                         if (!isIlluminationRequested && !mGoodCaptureReceived &&
                                 !exceedsVelocityThreshold) {
                             onFingerDown((int) event.getRawX(), (int) event.getRawY(), minor,
                                     major);
                             Log.v(TAG, "onTouch | finger down: " + touchInfo);
-                            mTouchLogTime = SystemClock.elapsedRealtime();
-                            mPowerManager.userActivity(SystemClock.uptimeMillis(),
+                            mTouchLogTime = mSystemClock.elapsedRealtime();
+                            mPowerManager.userActivity(mSystemClock.uptimeMillis(),
                                     PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
                             handled = true;
                         } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) {
                             Log.v(TAG, "onTouch | finger move: " + touchInfo);
-                            mTouchLogTime = SystemClock.elapsedRealtime();
+                            mTouchLogTime = mSystemClock.elapsedRealtime();
                         }
                     } else {
                         Log.v(TAG, "onTouch | finger outside");
@@ -530,7 +530,8 @@
             @NonNull KeyguardBypassController keyguardBypassController,
             @NonNull DisplayManager displayManager,
             @Main Handler mainHandler,
-            @NonNull ConfigurationController configurationController) {
+            @NonNull ConfigurationController configurationController,
+            @NonNull SystemClock systemClock) {
         mContext = context;
         mExecution = execution;
         // TODO (b/185124905): inject main handler and vibrator once done prototyping
@@ -566,6 +567,7 @@
                 mainHandler);
         mKeyguardBypassController = keyguardBypassController;
         mConfigurationController = configurationController;
+        mSystemClock = systemClock;
 
         mSensorProps = findFirstUdfps();
         // At least one UDFPS sensor exists
@@ -708,15 +710,21 @@
         return mCoreLayoutParams;
     }
 
+
     private void onOrientationChanged() {
         // When the configuration changes it's almost always necessary to destroy and re-create
         // the overlay's window to pass it the new LayoutParams.
         // Hiding the overlay will destroy its window. It's safe to hide the overlay regardless
         // of whether it is already hidden.
+        final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth();
         hideUdfpsOverlay();
+
         // If the overlay needs to be shown, this will re-create and show the overlay with the
         // updated LayoutParams. Otherwise, the overlay will remain hidden.
         updateOverlay();
+        if (wasShowingAltAuth) {
+            mKeyguardViewManager.showGenericBouncer(true);
+        }
     }
 
     private void showUdfpsOverlay(@NonNull ServerRequest request) {
@@ -787,6 +795,7 @@
                         mKeyguardViewMediator,
                         mLockscreenShadeTransitionController,
                         mConfigurationController,
+                        mSystemClock,
                         mKeyguardStateController,
                         this
                 );
@@ -823,10 +832,14 @@
             Log.v(TAG, "hideUdfpsOverlay | removing window");
             // Reset the controller back to its starting state.
             onFingerUp();
+            boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth();
             mWindowManager.removeView(mView);
             mView.setOnTouchListener(null);
             mView.setOnHoverListener(null);
             mView.setAnimationViewController(null);
+            if (wasShowingAltAuth) {
+                mKeyguardViewManager.resetAlternateAuth(true);
+            }
             mAccessibilityManager.removeTouchExplorationStateChangeListener(
                     mTouchExplorationStateChangeListener);
             mView = null;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
index 2034ff3..d407756 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
@@ -16,11 +16,9 @@
 
 package com.android.systemui.biometrics;
 
-import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.content.Context;
-import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -28,17 +26,11 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.TypedValue;
 import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.LinearInterpolator;
 
-import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.R;
 
 /**
@@ -47,20 +39,10 @@
 public class UdfpsEnrollDrawable extends UdfpsDrawable {
     private static final String TAG = "UdfpsAnimationEnroll";
 
-    private static final long HINT_COLOR_ANIM_DELAY_MS = 233L;
-    private static final long HINT_COLOR_ANIM_DURATION_MS = 517L;
-    private static final long HINT_WIDTH_ANIM_DURATION_MS = 233L;
-    private static final long TARGET_ANIM_DURATION_LONG = 800L;
-    private static final long TARGET_ANIM_DURATION_SHORT = 600L;
+    private static final long ANIM_DURATION = 800;
     // 1 + SCALE_MAX is the maximum that the moving target will animate to
     private static final float SCALE_MAX = 0.25f;
 
-    private static final float HINT_PADDING_DP = 10f;
-    private static final float HINT_MAX_WIDTH_DP = 6f;
-    private static final float HINT_ANGLE = 40f;
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-
     @NonNull private final Drawable mMovingTargetFpIcon;
     @NonNull private final Paint mSensorOutlinePaint;
     @NonNull private final Paint mBlueFill;
@@ -69,41 +51,17 @@
     @Nullable private UdfpsEnrollHelper mEnrollHelper;
 
     // Moving target animator set
-    @Nullable AnimatorSet mTargetAnimatorSet;
+    @Nullable AnimatorSet mAnimatorSet;
     // Moving target location
     float mCurrentX;
     float mCurrentY;
     // Moving target size
     float mCurrentScale = 1.f;
 
-    @ColorInt private final int mHintColorFaded;
-    @ColorInt private final int mHintColorHighlight;
-    private final float mHintMaxWidthPx;
-    private final float mHintPaddingPx;
-
-    @NonNull private final Animator.AnimatorListener mTargetAnimListener;
-
-    private boolean mShouldShowTipHint = false;
-    @NonNull private final Paint mTipHintPaint;
-    @Nullable private AnimatorSet mTipHintAnimatorSet;
-    @Nullable private ValueAnimator mTipHintColorAnimator;
-    @Nullable private ValueAnimator mTipHintWidthAnimator;
-    @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintColorUpdateListener;
-    @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintWidthUpdateListener;
-    @NonNull private final Animator.AnimatorListener mTipHintPulseListener;
-
-    private boolean mShouldShowEdgeHint = false;
-    @NonNull private final Paint mEdgeHintPaint;
-    @Nullable private AnimatorSet mEdgeHintAnimatorSet;
-    @Nullable private ValueAnimator mEdgeHintColorAnimator;
-    @Nullable private ValueAnimator mEdgeHintWidthAnimator;
-    @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintColorUpdateListener;
-    @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintWidthUpdateListener;
-    @NonNull private final Animator.AnimatorListener mEdgeHintPulseListener;
-
     UdfpsEnrollDrawable(@NonNull Context context) {
         super(context);
 
+
         mSensorOutlinePaint = new Paint(0 /* flags */);
         mSensorOutlinePaint.setAntiAlias(true);
         mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon));
@@ -120,117 +78,6 @@
         mMovingTargetFpIcon.mutate();
 
         mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon));
-
-        mHintColorFaded = getHintColorFaded(context);
-        mHintColorHighlight = context.getColor(R.color.udfps_enroll_progress);
-        mHintMaxWidthPx = Utils.dpToPixels(context, HINT_MAX_WIDTH_DP);
-        mHintPaddingPx = Utils.dpToPixels(context, HINT_PADDING_DP);
-
-        mTargetAnimListener = new Animator.AnimatorListener() {
-            @Override
-            public void onAnimationStart(Animator animation) {}
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                updateTipHintVisibility();
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {}
-
-            @Override
-            public void onAnimationRepeat(Animator animation) {}
-        };
-
-        mTipHintPaint = new Paint(0 /* flags */);
-        mTipHintPaint.setAntiAlias(true);
-        mTipHintPaint.setColor(mHintColorFaded);
-        mTipHintPaint.setStyle(Paint.Style.STROKE);
-        mTipHintPaint.setStrokeCap(Paint.Cap.ROUND);
-        mTipHintPaint.setStrokeWidth(0f);
-        mTipHintColorUpdateListener = animation -> {
-            mTipHintPaint.setColor((int) animation.getAnimatedValue());
-            invalidateSelf();
-        };
-        mTipHintWidthUpdateListener = animation -> {
-            mTipHintPaint.setStrokeWidth((float) animation.getAnimatedValue());
-            invalidateSelf();
-        };
-        mTipHintPulseListener = new Animator.AnimatorListener() {
-            @Override
-            public void onAnimationStart(Animator animation) {}
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mHandler.postDelayed(() -> {
-                    mTipHintColorAnimator =
-                            ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorFaded);
-                    mTipHintColorAnimator.setInterpolator(new LinearInterpolator());
-                    mTipHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS);
-                    mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener);
-                    mTipHintColorAnimator.start();
-                }, HINT_COLOR_ANIM_DELAY_MS);
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {}
-
-            @Override
-            public void onAnimationRepeat(Animator animation) {}
-        };
-
-        mEdgeHintPaint = new Paint(0 /* flags */);
-        mEdgeHintPaint.setAntiAlias(true);
-        mEdgeHintPaint.setColor(mHintColorFaded);
-        mEdgeHintPaint.setStyle(Paint.Style.STROKE);
-        mEdgeHintPaint.setStrokeCap(Paint.Cap.ROUND);
-        mEdgeHintPaint.setStrokeWidth(0f);
-        mEdgeHintColorUpdateListener = animation -> {
-            mEdgeHintPaint.setColor((int) animation.getAnimatedValue());
-            invalidateSelf();
-        };
-        mEdgeHintWidthUpdateListener = animation -> {
-            mEdgeHintPaint.setStrokeWidth((float) animation.getAnimatedValue());
-            invalidateSelf();
-        };
-        mEdgeHintPulseListener = new Animator.AnimatorListener() {
-            @Override
-            public void onAnimationStart(Animator animation) {}
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mHandler.postDelayed(() -> {
-                    mEdgeHintColorAnimator =
-                            ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorFaded);
-                    mEdgeHintColorAnimator.setInterpolator(new LinearInterpolator());
-                    mEdgeHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS);
-                    mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener);
-                    mEdgeHintColorAnimator.start();
-                }, HINT_COLOR_ANIM_DELAY_MS);
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {}
-
-            @Override
-            public void onAnimationRepeat(Animator animation) {}
-        };
-    }
-
-    @ColorInt
-    private static int getHintColorFaded(@NonNull Context context) {
-        final TypedValue tv = new TypedValue();
-        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true);
-        final int alpha = (int) (tv.getFloat() * 255f);
-
-        final int[] attrs = new int[] {android.R.attr.colorControlNormal};
-        final TypedArray ta = context.obtainStyledAttributes(attrs);
-        try {
-            @ColorInt final int color = ta.getColor(0, context.getColor(R.color.white_disabled));
-            return ColorUtils.setAlphaComponent(color, alpha);
-        } finally {
-            ta.recycle();
-        }
     }
 
     void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) {
@@ -251,154 +98,41 @@
     }
 
     void onEnrollmentProgress(int remaining, int totalSteps) {
-        if (mEnrollHelper == null) {
-            return;
-        }
-
-        if (!mEnrollHelper.isCenterEnrollmentStage()) {
-            if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) {
-                mTargetAnimatorSet.end();
+        if (mEnrollHelper.isCenterEnrollmentComplete()) {
+            if (mAnimatorSet != null && mAnimatorSet.isRunning()) {
+                mAnimatorSet.end();
             }
 
             final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint();
-            if (mCurrentX != point.x || mCurrentY != point.y) {
-                final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
-                x.addUpdateListener(animation -> {
-                    mCurrentX = (float) animation.getAnimatedValue();
-                    invalidateSelf();
-                });
 
-                final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
-                y.addUpdateListener(animation -> {
-                    mCurrentY = (float) animation.getAnimatedValue();
-                    invalidateSelf();
-                });
+            final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
+            x.addUpdateListener(animation -> {
+                mCurrentX = (float) animation.getAnimatedValue();
+                invalidateSelf();
+            });
 
-                final boolean isMovingToCenter = point.x == 0f && point.y == 0f;
-                final long duration = isMovingToCenter
-                        ? TARGET_ANIM_DURATION_SHORT
-                        : TARGET_ANIM_DURATION_LONG;
+            final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
+            y.addUpdateListener(animation -> {
+                mCurrentY = (float) animation.getAnimatedValue();
+                invalidateSelf();
+            });
 
-                final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
-                scale.setDuration(duration);
-                scale.addUpdateListener(animation -> {
-                    // Grow then shrink
-                    mCurrentScale = 1
-                            + SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
-                    invalidateSelf();
-                });
+            final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
+            scale.setDuration(ANIM_DURATION);
+            scale.addUpdateListener(animation -> {
+                // Grow then shrink
+                mCurrentScale = 1 +
+                        SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
+                invalidateSelf();
+            });
 
-                mTargetAnimatorSet = new AnimatorSet();
+            mAnimatorSet = new AnimatorSet();
 
-                mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
-                mTargetAnimatorSet.setDuration(duration);
-                mTargetAnimatorSet.addListener(mTargetAnimListener);
-                mTargetAnimatorSet.playTogether(x, y, scale);
-                mTargetAnimatorSet.start();
-            } else {
-                updateTipHintVisibility();
-            }
-        } else {
-            updateTipHintVisibility();
+            mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+            mAnimatorSet.setDuration(ANIM_DURATION);
+            mAnimatorSet.playTogether(x, y, scale);
+            mAnimatorSet.start();
         }
-
-        updateEdgeHintVisibility();
-    }
-
-    private void updateTipHintVisibility() {
-        final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage();
-        if (mShouldShowTipHint == shouldShow) {
-            return;
-        }
-        mShouldShowTipHint = shouldShow;
-
-        if (mTipHintWidthAnimator != null && mTipHintWidthAnimator.isRunning()) {
-            mTipHintWidthAnimator.cancel();
-        }
-
-        final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f;
-        mTipHintWidthAnimator = ValueAnimator.ofFloat(mTipHintPaint.getStrokeWidth(), targetWidth);
-        mTipHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
-        mTipHintWidthAnimator.addUpdateListener(mTipHintWidthUpdateListener);
-
-        if (shouldShow) {
-            startTipHintPulseAnimation();
-        } else {
-            mTipHintWidthAnimator.start();
-        }
-    }
-
-    private void updateEdgeHintVisibility() {
-        final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isEdgeEnrollmentStage();
-        if (mShouldShowEdgeHint == shouldShow) {
-            return;
-        }
-        mShouldShowEdgeHint = shouldShow;
-
-        if (mEdgeHintWidthAnimator != null && mEdgeHintWidthAnimator.isRunning()) {
-            mEdgeHintWidthAnimator.cancel();
-        }
-
-        final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f;
-        mEdgeHintWidthAnimator =
-                ValueAnimator.ofFloat(mEdgeHintPaint.getStrokeWidth(), targetWidth);
-        mEdgeHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
-        mEdgeHintWidthAnimator.addUpdateListener(mEdgeHintWidthUpdateListener);
-
-        if (shouldShow) {
-            startEdgeHintPulseAnimation();
-        } else {
-            mEdgeHintWidthAnimator.start();
-        }
-    }
-
-    private void startTipHintPulseAnimation() {
-        mHandler.removeCallbacksAndMessages(null);
-        if (mTipHintAnimatorSet != null && mTipHintAnimatorSet.isRunning()) {
-            mTipHintAnimatorSet.cancel();
-        }
-        if (mTipHintColorAnimator != null && mTipHintColorAnimator.isRunning()) {
-            mTipHintColorAnimator.cancel();
-        }
-
-        mTipHintColorAnimator = ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorHighlight);
-        mTipHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
-        mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener);
-        mTipHintColorAnimator.addListener(mTipHintPulseListener);
-
-        mTipHintAnimatorSet = new AnimatorSet();
-        mTipHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
-        mTipHintAnimatorSet.playTogether(mTipHintColorAnimator, mTipHintWidthAnimator);
-        mTipHintAnimatorSet.start();
-    }
-
-    private void startEdgeHintPulseAnimation() {
-        mHandler.removeCallbacksAndMessages(null);
-        if (mEdgeHintAnimatorSet != null && mEdgeHintAnimatorSet.isRunning()) {
-            mEdgeHintAnimatorSet.cancel();
-        }
-        if (mEdgeHintColorAnimator != null && mEdgeHintColorAnimator.isRunning()) {
-            mEdgeHintColorAnimator.cancel();
-        }
-
-        mEdgeHintColorAnimator =
-                ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorHighlight);
-        mEdgeHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
-        mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener);
-        mEdgeHintColorAnimator.addListener(mEdgeHintPulseListener);
-
-        mEdgeHintAnimatorSet = new AnimatorSet();
-        mEdgeHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
-        mEdgeHintAnimatorSet.playTogether(mEdgeHintColorAnimator, mEdgeHintWidthAnimator);
-        mEdgeHintAnimatorSet.start();
-    }
-
-    private boolean isTipHintVisible() {
-        return mTipHintPaint.getStrokeWidth() > 0f;
-    }
-
-    private boolean isEdgeHintVisible() {
-        return mEdgeHintPaint.getStrokeWidth() > 0f;
     }
 
     @Override
@@ -408,7 +142,7 @@
         }
 
         // Draw moving target
-        if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) {
+        if (mEnrollHelper.isCenterEnrollmentComplete()) {
             canvas.save();
             canvas.translate(mCurrentX, mCurrentY);
 
@@ -428,59 +162,6 @@
             mFingerprintDrawable.setAlpha(mAlpha);
             mSensorOutlinePaint.setAlpha(mAlpha);
         }
-
-        // Draw the finger tip or edges hint.
-        if (isTipHintVisible() || isEdgeHintVisible()) {
-            canvas.save();
-
-            // Make arcs start from the top, rather than the right.
-            canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY());
-
-            final float halfSensorHeight = Math.abs(mSensorRect.bottom - mSensorRect.top) / 2f;
-            final float halfSensorWidth = Math.abs(mSensorRect.right - mSensorRect.left) / 2f;
-            final float hintXOffset = halfSensorWidth + mHintPaddingPx;
-            final float hintYOffset = halfSensorHeight + mHintPaddingPx;
-
-            if (isTipHintVisible()) {
-                canvas.drawArc(
-                        mSensorRect.centerX() - hintXOffset,
-                        mSensorRect.centerY() - hintYOffset,
-                        mSensorRect.centerX() + hintXOffset,
-                        mSensorRect.centerY() + hintYOffset,
-                        -HINT_ANGLE / 2f,
-                        HINT_ANGLE,
-                        false /* useCenter */,
-                        mTipHintPaint);
-            }
-
-            if (isEdgeHintVisible()) {
-                // Draw right edge hint.
-                canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY());
-                canvas.drawArc(
-                        mSensorRect.centerX() - hintXOffset,
-                        mSensorRect.centerY() - hintYOffset,
-                        mSensorRect.centerX() + hintXOffset,
-                        mSensorRect.centerY() + hintYOffset,
-                        -HINT_ANGLE / 2f,
-                        HINT_ANGLE,
-                        false /* useCenter */,
-                        mEdgeHintPaint);
-
-                // Draw left edge hint.
-                canvas.rotate(180f, mSensorRect.centerX(), mSensorRect.centerY());
-                canvas.drawArc(
-                        mSensorRect.centerX() - hintXOffset,
-                        mSensorRect.centerY() - hintYOffset,
-                        mSensorRect.centerX() + hintXOffset,
-                        mSensorRect.centerY() + hintYOffset,
-                        -HINT_ANGLE / 2f,
-                        HINT_ANGLE,
-                        false /* useCenter */,
-                        mEdgeHintPaint);
-            }
-
-            canvas.restore();
-        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index 8ac6df7..19148e3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -44,20 +44,13 @@
     private static final String NEW_COORDS_OVERRIDE =
             "com.android.systemui.biometrics.UdfpsNewCoords";
 
-    static final int ENROLL_STAGE_COUNT = 4;
-
-    // TODO(b/198928407): Consolidate with FingerprintEnrollEnrolling
-    private static final int[] STAGE_THRESHOLDS = new int[] {
-            2, // center
-            18, // guided
-            22, // fingertip
-            38, // edges
-    };
+    // Enroll with two center touches before going to guided enrollment
+    private static final int NUM_CENTER_TOUCHES = 2;
 
     interface Listener {
         void onEnrollmentProgress(int remaining, int totalSteps);
-        void onEnrollmentHelp(int remaining, int totalSteps);
         void onLastStepAcquired();
+        void onEnrollmentHelp();
     }
 
     @NonNull private final Context mContext;
@@ -73,8 +66,6 @@
     // interface makes no promises about monotonically increasing by one each time.
     private int mLocationsEnrolled = 0;
 
-    private int mCenterTouchCount = 0;
-
     @Nullable Listener mListener;
 
     public UdfpsEnrollHelper(@NonNull Context context, int reason) {
@@ -127,43 +118,17 @@
         }
     }
 
-    static int getStageThreshold(int index) {
-        return STAGE_THRESHOLDS[index];
-    }
-
-    static int getLastStageThreshold() {
-        return STAGE_THRESHOLDS[ENROLL_STAGE_COUNT - 1];
-    }
-
     boolean shouldShowProgressBar() {
         return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING;
     }
 
     void onEnrollmentProgress(int remaining) {
-        Log.d(TAG, "onEnrollmentProgress: remaining = " + remaining
-                + ", mRemainingSteps = " + mRemainingSteps
-                + ", mTotalSteps = " + mTotalSteps
-                + ", mLocationsEnrolled = " + mLocationsEnrolled
-                + ", mCenterTouchCount = " + mCenterTouchCount);
+        if (mTotalSteps == -1) {
+            mTotalSteps = remaining;
+        }
 
         if (remaining != mRemainingSteps) {
             mLocationsEnrolled++;
-            if (isCenterEnrollmentStage()) {
-                mCenterTouchCount++;
-            }
-        }
-
-        if (mTotalSteps == -1) {
-            mTotalSteps = remaining;
-
-            // Allocate (or subtract) any extra steps for the first enroll stage.
-            final int extraSteps = mTotalSteps - getLastStageThreshold();
-            if (extraSteps != 0) {
-                for (int stageIndex = 0; stageIndex < ENROLL_STAGE_COUNT; stageIndex++) {
-                    STAGE_THRESHOLDS[stageIndex] =
-                            Math.max(0, STAGE_THRESHOLDS[stageIndex] + extraSteps);
-                }
-            }
         }
 
         mRemainingSteps = remaining;
@@ -175,7 +140,7 @@
 
     void onEnrollmentHelp() {
         if (mListener != null) {
-            mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps);
+            mListener.onEnrollmentHelp();
         }
     }
 
@@ -190,39 +155,19 @@
         }
     }
 
-    boolean isCenterEnrollmentStage() {
-        if (mTotalSteps == -1 || mRemainingSteps == -1) {
-            return true;
-        }
-        return mTotalSteps - mRemainingSteps < STAGE_THRESHOLDS[0];
-    }
-
-    boolean isGuidedEnrollmentStage() {
-        if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) {
-            return false;
-        }
-        final int progressSteps = mTotalSteps - mRemainingSteps;
-        return progressSteps >= STAGE_THRESHOLDS[0] && progressSteps < STAGE_THRESHOLDS[1];
-    }
-
-    boolean isTipEnrollmentStage() {
+    boolean isCenterEnrollmentComplete() {
         if (mTotalSteps == -1 || mRemainingSteps == -1) {
             return false;
-        }
-        final int progressSteps = mTotalSteps - mRemainingSteps;
-        return progressSteps >= STAGE_THRESHOLDS[1] && progressSteps < STAGE_THRESHOLDS[2];
-    }
-
-    boolean isEdgeEnrollmentStage() {
-        if (mTotalSteps == -1 || mRemainingSteps == -1) {
+        } else if (mAccessibilityEnabled) {
             return false;
         }
-        return mTotalSteps - mRemainingSteps >= STAGE_THRESHOLDS[2];
+        final int stepsEnrolled = mTotalSteps - mRemainingSteps;
+        return stepsEnrolled >= NUM_CENTER_TOUCHES;
     }
 
     @NonNull
     PointF getNextGuidedEnrollmentPoint() {
-        if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) {
+        if (mAccessibilityEnabled) {
             return new PointF(0f, 0f);
         }
 
@@ -232,14 +177,13 @@
                     SCALE_OVERRIDE, SCALE,
                     UserHandle.USER_CURRENT);
         }
-        final int index = mLocationsEnrolled - mCenterTouchCount;
+        final int index = mLocationsEnrolled - NUM_CENTER_TOUCHES;
         final PointF originalPoint = mGuidedEnrollmentPoints
                 .get(index % mGuidedEnrollmentPoints.size());
         return new PointF(originalPoint.x * scale, originalPoint.y * scale);
     }
 
     void animateIfLastStep() {
-        Log.d(TAG, "animateIfLastStep: mRemainingSteps = " + mRemainingSteps);
         if (mListener == null) {
             Log.e(TAG, "animateIfLastStep, null listener");
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index b56543f..373d17c8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -16,107 +16,195 @@
 
 package com.android.systemui.biometrics;
 
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
+import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
 import android.util.Log;
+import android.util.TypedValue;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import java.util.ArrayList;
-import java.util.List;
+import com.android.systemui.R;
 
 /**
  * UDFPS enrollment progress bar.
  */
 public class UdfpsEnrollProgressBarDrawable extends Drawable {
-    private static final String TAG = "UdfpsProgressBar";
 
-    private static final float SEGMENT_GAP_ANGLE = 12f;
+    private static final String TAG = "UdfpsEnrollProgressBarDrawable";
 
-    @NonNull private final List<UdfpsEnrollProgressBarSegment> mSegments;
+    private static final float PROGRESS_BAR_THICKNESS_DP = 12;
+
+    @NonNull private final Context mContext;
+    @NonNull private final Paint mBackgroundCirclePaint;
+    @NonNull private final Paint mProgressPaint;
+
+    @Nullable private ValueAnimator mProgressAnimator;
+    @Nullable private ValueAnimator mProgressShowingHelpAnimator;
+    @Nullable private ValueAnimator mProgressHidingHelpAnimator;
+    @ColorInt private final int mProgressColor;
+    @ColorInt private final int mProgressHelpColor;
+    private final int mShortAnimationDuration;
+    private float mProgress;
+    private int mRotation; // After last step, rotate the progress bar once
+    private boolean mLastStepAcquired;
 
     public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
-        mSegments = new ArrayList<>(UdfpsEnrollHelper.ENROLL_STAGE_COUNT);
-        float startAngle = SEGMENT_GAP_ANGLE / 2f;
-        final float sweepAngle = (360f / UdfpsEnrollHelper.ENROLL_STAGE_COUNT) - SEGMENT_GAP_ANGLE;
-        final Runnable invalidateRunnable = this::invalidateSelf;
-        for (int index = 0; index < UdfpsEnrollHelper.ENROLL_STAGE_COUNT; index++) {
-            mSegments.add(new UdfpsEnrollProgressBarSegment(context, getBounds(), startAngle,
-                    sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable));
-            startAngle += sweepAngle + SEGMENT_GAP_ANGLE;
-        }
+        mContext = context;
+
+        mShortAnimationDuration = context.getResources()
+                .getInteger(com.android.internal.R.integer.config_shortAnimTime);
+        mProgressColor = context.getColor(R.color.udfps_enroll_progress);
+        mProgressHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
+
+        mBackgroundCirclePaint = new Paint();
+        mBackgroundCirclePaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP));
+        mBackgroundCirclePaint.setColor(context.getColor(R.color.white_disabled));
+        mBackgroundCirclePaint.setAntiAlias(true);
+        mBackgroundCirclePaint.setStyle(Paint.Style.STROKE);
+
+        // Background circle color + alpha
+        TypedArray tc = context.obtainStyledAttributes(
+                new int[] {android.R.attr.colorControlNormal});
+        int tintColor = tc.getColor(0, mBackgroundCirclePaint.getColor());
+        mBackgroundCirclePaint.setColor(tintColor);
+        tc.recycle();
+        TypedValue alpha = new TypedValue();
+        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
+        mBackgroundCirclePaint.setAlpha((int) (alpha.getFloat() * 255));
+
+        // Progress should not be color extracted
+        mProgressPaint = new Paint();
+        mProgressPaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP));
+        mProgressPaint.setColor(mProgressColor);
+        mProgressPaint.setAntiAlias(true);
+        mProgressPaint.setStyle(Paint.Style.STROKE);
+        mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
     }
 
     void setEnrollmentProgress(int remaining, int totalSteps) {
-        if (remaining == totalSteps) {
-            // Show some progress for the initial touch.
-            setEnrollmentProgress(1);
-        } else {
-            setEnrollmentProgress(totalSteps - remaining);
-        }
+        // Add one so that the first steps actually changes progress, but also so that the last
+        // step ends at 1.0
+        final float progress = (totalSteps - remaining + 1) / (float) (totalSteps + 1);
+        setEnrollmentProgress(progress);
     }
 
-    private void setEnrollmentProgress(int progressSteps) {
-        Log.d(TAG, "setEnrollmentProgress: progressSteps = " + progressSteps);
-
-        int segmentIndex = 0;
-        int prevThreshold = 0;
-        while (segmentIndex < mSegments.size()) {
-            final UdfpsEnrollProgressBarSegment segment = mSegments.get(segmentIndex);
-            final int threshold = UdfpsEnrollHelper.getStageThreshold(segmentIndex);
-
-            if (progressSteps >= threshold && !segment.isFilledOrFilling()) {
-                Log.d(TAG, "setEnrollmentProgress: segment[" + segmentIndex + "] complete");
-                segment.updateProgress(1f);
-                break;
-            } else if (progressSteps >= prevThreshold && progressSteps < threshold) {
-                final int relativeSteps = progressSteps - prevThreshold;
-                final int relativeThreshold = threshold - prevThreshold;
-                final float segmentProgress = (float) relativeSteps / (float) relativeThreshold;
-                Log.d(TAG, "setEnrollmentProgress: segment[" + segmentIndex + "] progress = "
-                        + segmentProgress);
-                segment.updateProgress(segmentProgress);
-                break;
-            }
-
-            segmentIndex++;
-            prevThreshold = threshold;
+    private void setEnrollmentProgress(float progress) {
+        if (mLastStepAcquired) {
+            return;
         }
 
-        if (progressSteps >= UdfpsEnrollHelper.getLastStageThreshold()) {
-            Log.d(TAG, "setEnrollmentProgress: startCompletionAnimation");
-            for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
-                segment.startCompletionAnimation();
-            }
-        } else {
-            Log.d(TAG, "setEnrollmentProgress: cancelCompletionAnimation");
-            for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
-                segment.cancelCompletionAnimation();
-            }
+        long animationDuration = mShortAnimationDuration;
+
+        hideEnrollmentHelp();
+
+        if (progress == 1.f) {
+            animationDuration = 400;
+            final ValueAnimator rotationAnimator = ValueAnimator.ofInt(0, 400);
+            rotationAnimator.setDuration(animationDuration);
+            rotationAnimator.addUpdateListener(animation -> {
+                Log.d(TAG, "Rotation: " + mRotation);
+                mRotation = (int) animation.getAnimatedValue();
+                invalidateSelf();
+            });
+            rotationAnimator.start();
         }
+
+        if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
+            mProgressAnimator.cancel();
+        }
+
+        mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress);
+        mProgressAnimator.setDuration(animationDuration);
+        mProgressAnimator.addUpdateListener(animation -> {
+            mProgress = (float) animation.getAnimatedValue();
+            invalidateSelf();
+        });
+        mProgressAnimator.start();
     }
 
     void onLastStepAcquired() {
-        Log.d(TAG, "setEnrollmentProgress: onLastStepAcquired");
-        setEnrollmentProgress(UdfpsEnrollHelper.getLastStageThreshold());
+        setEnrollmentProgress(1.f);
+        mLastStepAcquired = true;
+    }
+
+    void onEnrollmentHelp() {
+        if (mProgressShowingHelpAnimator != null || mProgressAnimator == null) {
+            return; // already showing or at 0% (no progress bar visible)
+        }
+
+        if (mProgressHidingHelpAnimator != null && mProgressHidingHelpAnimator.isRunning()) {
+            mProgressHidingHelpAnimator.cancel();
+        }
+        mProgressHidingHelpAnimator = null;
+
+        mProgressShowingHelpAnimator = getProgressColorAnimator(
+                mProgressPaint.getColor(), mProgressHelpColor);
+        mProgressShowingHelpAnimator.start();
+    }
+
+    private void hideEnrollmentHelp() {
+        if (mProgressHidingHelpAnimator != null || mProgressShowingHelpAnimator == null) {
+            return; // already hidden or help never shown
+        }
+
+        if (mProgressShowingHelpAnimator != null && mProgressShowingHelpAnimator.isRunning()) {
+            mProgressShowingHelpAnimator.cancel();
+        }
+        mProgressShowingHelpAnimator = null;
+
+        mProgressHidingHelpAnimator = getProgressColorAnimator(
+                mProgressPaint.getColor(), mProgressColor);
+        mProgressHidingHelpAnimator.start();
+    }
+
+    private ValueAnimator getProgressColorAnimator(@ColorInt int from, @ColorInt int to) {
+        final ValueAnimator animator = ValueAnimator.ofObject(
+                ArgbEvaluator.getInstance(), from, to);
+        animator.setDuration(mShortAnimationDuration);
+        animator.addUpdateListener(animation -> {
+            mProgressPaint.setColor((int) animation.getAnimatedValue());
+        });
+        return animator;
     }
 
     @Override
     public void draw(@NonNull Canvas canvas) {
-        Log.d(TAG, "setEnrollmentProgress: draw");
-
         canvas.save();
 
         // Progress starts from the top, instead of the right
-        canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());
+        canvas.rotate(-90 + mRotation, getBounds().centerX(), getBounds().centerY());
 
-        // Draw each of the enroll segments.
-        for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
-            segment.draw(canvas);
-        }
+        // Progress bar "background track"
+        final float halfPaddingPx = Utils.dpToPixels(mContext, PROGRESS_BAR_THICKNESS_DP) / 2;
+        canvas.drawArc(halfPaddingPx,
+                halfPaddingPx,
+                getBounds().right - halfPaddingPx,
+                getBounds().bottom - halfPaddingPx,
+                0,
+                360,
+                false,
+                mBackgroundCirclePaint
+        );
+
+        final float progress = 360.f * mProgress;
+        // Progress
+        canvas.drawArc(halfPaddingPx,
+                halfPaddingPx,
+                getBounds().right - halfPaddingPx,
+                getBounds().bottom - halfPaddingPx,
+                0,
+                progress,
+                false,
+                mProgressPaint
+        );
 
         canvas.restore();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java
deleted file mode 100644
index 5f24380..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2021 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.biometrics;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-import android.util.TypedValue;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.systemui.R;
-
-/**
- * A single segment of the UDFPS enrollment progress bar.
- */
-public class UdfpsEnrollProgressBarSegment {
-    private static final String TAG = "UdfpsProgressBarSegment";
-
-    private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
-    private static final long OVER_SWEEP_ANIMATION_DELAY_MS = 200L;
-    private static final long OVER_SWEEP_ANIMATION_DURATION_MS = 200L;
-
-    private static final float STROKE_WIDTH_DP = 12f;
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-
-    @NonNull private final Rect mBounds;
-    @NonNull private final Runnable mInvalidateRunnable;
-    private final float mStartAngle;
-    private final float mSweepAngle;
-    private final float mMaxOverSweepAngle;
-    private final float mStrokeWidthPx;
-
-    @NonNull private final Paint mBackgroundPaint;
-    @NonNull private final Paint mProgressPaint;
-
-    private boolean mIsFilledOrFilling = false;
-
-    private float mProgress = 0f;
-    @Nullable private ValueAnimator mProgressAnimator;
-    @NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
-
-    private float mOverSweepAngle = 0f;
-    @Nullable private ValueAnimator mOverSweepAnimator;
-    @Nullable private ValueAnimator mOverSweepReverseAnimator;
-    @NonNull private final ValueAnimator.AnimatorUpdateListener mOverSweepUpdateListener;
-    @NonNull private final Runnable mOverSweepAnimationRunnable;
-
-    public UdfpsEnrollProgressBarSegment(@NonNull Context context, @NonNull Rect bounds,
-            float startAngle, float sweepAngle, float maxOverSweepAngle,
-            @NonNull Runnable invalidateRunnable) {
-
-        mBounds = bounds;
-        mInvalidateRunnable = invalidateRunnable;
-        mStartAngle = startAngle;
-        mSweepAngle = sweepAngle;
-        mMaxOverSweepAngle = maxOverSweepAngle;
-        mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
-
-        mBackgroundPaint = new Paint();
-        mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
-        mBackgroundPaint.setColor(context.getColor(R.color.white_disabled));
-        mBackgroundPaint.setAntiAlias(true);
-        mBackgroundPaint.setStyle(Paint.Style.STROKE);
-        mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
-
-        // Background paint color + alpha
-        final int[] attrs = new int[] {android.R.attr.colorControlNormal};
-        final TypedArray ta = context.obtainStyledAttributes(attrs);
-        @ColorInt final int tintColor = ta.getColor(0, mBackgroundPaint.getColor());
-        mBackgroundPaint.setColor(tintColor);
-        ta.recycle();
-        TypedValue alpha = new TypedValue();
-        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
-        mBackgroundPaint.setAlpha((int) (alpha.getFloat() * 255f));
-
-        // Progress should not be color extracted
-        mProgressPaint = new Paint();
-        mProgressPaint.setStrokeWidth(mStrokeWidthPx);
-        mProgressPaint.setColor(context.getColor(R.color.udfps_enroll_progress));
-        mProgressPaint.setAntiAlias(true);
-        mProgressPaint.setStyle(Paint.Style.STROKE);
-        mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
-
-        mProgressUpdateListener = animation -> {
-            mProgress = (float) animation.getAnimatedValue();
-            mInvalidateRunnable.run();
-        };
-
-        mOverSweepUpdateListener = animation -> {
-            mOverSweepAngle = (float) animation.getAnimatedValue();
-            mInvalidateRunnable.run();
-        };
-        mOverSweepAnimationRunnable = () -> {
-            if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
-                mOverSweepAnimator.cancel();
-            }
-            mOverSweepAnimator = ValueAnimator.ofFloat(mOverSweepAngle, mMaxOverSweepAngle);
-            mOverSweepAnimator.setDuration(OVER_SWEEP_ANIMATION_DURATION_MS);
-            mOverSweepAnimator.addUpdateListener(mOverSweepUpdateListener);
-            mOverSweepAnimator.start();
-        };
-    }
-
-    /**
-     * Draws this segment to the given canvas.
-     */
-    public void draw(@NonNull Canvas canvas) {
-        Log.d(TAG, "draw: mProgress = " + mProgress);
-
-        final float halfPaddingPx = mStrokeWidthPx / 2f;
-
-        if (mProgress < 1f) {
-            // Draw the unfilled background color of the segment.
-            canvas.drawArc(
-                    halfPaddingPx,
-                    halfPaddingPx,
-                    mBounds.right - halfPaddingPx,
-                    mBounds.bottom - halfPaddingPx,
-                    mStartAngle,
-                    mSweepAngle,
-                    false /* useCenter */,
-                    mBackgroundPaint);
-        }
-
-        if (mProgress > 0f) {
-            // Draw the filled progress portion of the segment.
-            canvas.drawArc(
-                    halfPaddingPx,
-                    halfPaddingPx,
-                    mBounds.right - halfPaddingPx,
-                    mBounds.bottom - halfPaddingPx,
-                    mStartAngle,
-                    mSweepAngle * mProgress + mOverSweepAngle,
-                    false /* useCenter */,
-                    mProgressPaint);
-        }
-    }
-
-    /**
-     * @return Whether this segment is filled or in the process of being filled.
-     */
-    public boolean isFilledOrFilling() {
-        return mIsFilledOrFilling;
-    }
-
-    /**
-     * Updates the fill progress of this segment, animating if necessary.
-     *
-     * @param progress The new fill progress, in the range [0, 1].
-     */
-    public void updateProgress(float progress) {
-        updateProgress(progress, PROGRESS_ANIMATION_DURATION_MS);
-    }
-
-    private void updateProgress(float progress, long animationDurationMs) {
-        Log.d(TAG, "updateProgress: progress = " + progress
-                + ", duration = " + animationDurationMs);
-
-        if (mProgress == progress) {
-            Log.d(TAG, "updateProgress skipped: progress == mProgress");
-            return;
-        }
-
-        mIsFilledOrFilling = progress >= 1f;
-
-        if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
-            mProgressAnimator.cancel();
-        }
-
-        mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress);
-        mProgressAnimator.setDuration(animationDurationMs);
-        mProgressAnimator.addUpdateListener(mProgressUpdateListener);
-        mProgressAnimator.start();
-    }
-
-    /**
-     * Queues and runs the completion animation for this segment.
-     */
-    public void startCompletionAnimation() {
-        final boolean hasCallback = mHandler.hasCallbacks(mOverSweepAnimationRunnable);
-        if (hasCallback || mOverSweepAngle >= mMaxOverSweepAngle) {
-            Log.d(TAG, "startCompletionAnimation skipped: hasCallback = " + hasCallback
-                    + ", mOverSweepAngle = " + mOverSweepAngle);
-            return;
-        }
-
-        Log.d(TAG, "startCompletionAnimation: mProgress = " + mProgress
-                + ", mOverSweepAngle = " + mOverSweepAngle);
-
-        // Reset sweep angle back to zero if the animation is being rolled back.
-        if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
-            mOverSweepReverseAnimator.cancel();
-            mOverSweepAngle = 0f;
-        }
-
-        // Start filling the segment if it isn't already.
-        if (mProgress < 1f) {
-            updateProgress(1f, OVER_SWEEP_ANIMATION_DELAY_MS);
-        }
-
-        // Queue the animation to run after fill completes.
-        mHandler.postDelayed(mOverSweepAnimationRunnable, OVER_SWEEP_ANIMATION_DELAY_MS);
-    }
-
-    /**
-     * Cancels (and reverses, if necessary) a queued or running completion animation.
-     */
-    public void cancelCompletionAnimation() {
-        Log.d(TAG, "cancelCompletionAnimation: mProgress = " + mProgress
-                + ", mOverSweepAngle = " + mOverSweepAngle);
-
-        // Cancel the animation if it's queued or running.
-        mHandler.removeCallbacks(mOverSweepAnimationRunnable);
-        if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
-            mOverSweepAnimator.cancel();
-        }
-
-        // Roll back the animation if it has at least partially run.
-        if (mOverSweepAngle > 0f) {
-            if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
-                mOverSweepReverseAnimator.cancel();
-            }
-
-            final float completion = mOverSweepAngle / mMaxOverSweepAngle;
-            final long proratedDuration = (long) (OVER_SWEEP_ANIMATION_DURATION_MS * completion);
-            mOverSweepReverseAnimator = ValueAnimator.ofFloat(mOverSweepAngle, 0f);
-            mOverSweepReverseAnimator.setDuration(proratedDuration);
-            mOverSweepReverseAnimator.addUpdateListener(mOverSweepUpdateListener);
-            mOverSweepReverseAnimator.start();
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 6f02c64..d9edef4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -83,14 +83,13 @@
         });
     }
 
-    void onEnrollmentHelp(int remaining, int totalSteps) {
-        mHandler.post(
-                () -> mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps));
-    }
-
     void onLastStepAcquired() {
         mHandler.post(() -> {
             mFingerprintProgressDrawable.onLastStepAcquired();
         });
     }
+
+    void onEnrollmentHelp() {
+        mHandler.post(mFingerprintProgressDrawable::onEnrollmentHelp);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index 6cdd1c8b..61534a5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -33,21 +33,21 @@
     @NonNull private final UdfpsEnrollHelper mEnrollHelper;
     @NonNull private final UdfpsEnrollHelper.Listener mEnrollHelperListener =
             new UdfpsEnrollHelper.Listener() {
-                @Override
-                public void onEnrollmentProgress(int remaining, int totalSteps) {
-                    mView.onEnrollmentProgress(remaining, totalSteps);
-                }
+        @Override
+        public void onEnrollmentProgress(int remaining, int totalSteps) {
+            mView.onEnrollmentProgress(remaining, totalSteps);
+        }
 
-                @Override
-                public void onEnrollmentHelp(int remaining, int totalSteps) {
-                    mView.onEnrollmentHelp(remaining, totalSteps);
-                }
+        @Override
+        public void onLastStepAcquired() {
+            mView.onLastStepAcquired();
+        }
 
-                @Override
-                public void onLastStepAcquired() {
-                    mView.onLastStepAcquired();
-                }
-            };
+        @Override
+        public void onEnrollmentHelp() {
+            mView.onEnrollmentHelp();
+        }
+    };
 
     protected UdfpsEnrollViewController(
             @NonNull UdfpsEnrollView view,
@@ -79,7 +79,7 @@
     @NonNull
     @Override
     public PointF getTouchTranslation() {
-        if (!mEnrollHelper.isGuidedEnrollmentStage()) {
+        if (!mEnrollHelper.isCenterEnrollmentComplete()) {
             return new PointF(0, 0);
         } else {
             return mEnrollHelper.getNextGuidedEnrollmentPoint();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 22d7a3ff..8aa545a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -36,6 +36,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.SystemClock;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -51,6 +52,7 @@
     @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
     @NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController;
     @NonNull private final ConfigurationController mConfigurationController;
+    @NonNull private final SystemClock mSystemClock;
     @NonNull private final KeyguardStateController mKeyguardStateController;
     @NonNull private final UdfpsController mUdfpsController;
 
@@ -61,7 +63,7 @@
     private int mStatusBarState;
     private float mTransitionToFullShadeProgress;
     private float mLastDozeAmount;
-
+    private long mLastUdfpsBouncerShowTime = -1;
     private float mStatusBarExpansion;
     private boolean mLaunchTransitionFadingAway;
 
@@ -84,6 +86,7 @@
             @NonNull KeyguardViewMediator keyguardViewMediator,
             @NonNull LockscreenShadeTransitionController transitionController,
             @NonNull ConfigurationController configurationController,
+            @NonNull SystemClock systemClock,
             @NonNull KeyguardStateController keyguardStateController,
             @NonNull UdfpsController udfpsController) {
         super(view, statusBarStateController, statusBar, dumpManager);
@@ -93,6 +96,7 @@
         mKeyguardViewMediator = keyguardViewMediator;
         mLockScreenShadeTransitionController = transitionController;
         mConfigurationController = configurationController;
+        mSystemClock = systemClock;
         mKeyguardStateController = keyguardStateController;
         mUdfpsController = udfpsController;
     }
@@ -103,6 +107,12 @@
     }
 
     @Override
+    public void onInit() {
+        super.onInit();
+        mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
+    }
+
+    @Override
     protected void onViewAttached() {
         super.onViewAttached();
         final float dozeAmount = mStatusBarStateController.getDozeAmount();
@@ -171,6 +181,9 @@
         boolean udfpsAffordanceWasNotShowing = shouldPauseAuth();
         mShowingUdfpsBouncer = show;
         if (mShowingUdfpsBouncer) {
+            mLastUdfpsBouncerShowTime = mSystemClock.uptimeMillis();
+        }
+        if (mShowingUdfpsBouncer) {
             if (udfpsAffordanceWasNotShowing) {
                 mView.animateInUdfpsBouncer(null);
             }
@@ -238,16 +251,25 @@
      * If we were previously showing the udfps bouncer, hide it and instead show the regular
      * (pin/pattern/password) bouncer.
      *
-     * Does nothing if we weren't previously showing the udfps bouncer.
+     * Does nothing if we weren't previously showing the UDFPS bouncer.
      */
     private void maybeShowInputBouncer() {
-        if (mShowingUdfpsBouncer) {
+        if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
             mKeyguardViewManager.showBouncer(true);
             mKeyguardViewManager.resetAlternateAuth(false);
         }
     }
 
     /**
+     * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside
+     * of the udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password
+     * bouncer.
+     */
+    private boolean hasUdfpsBouncerShownWithMinTime() {
+        return (mSystemClock.uptimeMillis() - mLastUdfpsBouncerShowTime) > 200;
+    }
+
+    /**
      * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
      * transitioning yet, while 1.0f means we've fully dragged down.
      */
@@ -265,7 +287,9 @@
                 : (int) MathUtils.constrain(
                     MathUtils.map(.5f, .9f, 0f, 255f, expansion),
                     0f, 255f);
-        alpha *= (1.0f - mTransitionToFullShadeProgress);
+        if (!mShowingUdfpsBouncer) {
+            alpha *= (1.0f - mTransitionToFullShadeProgress);
+        }
         mView.setUnpausedAlpha(alpha);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
index e1349f2..40c28fa 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
@@ -21,6 +21,7 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_PRIMARY_DEVIANCE;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE;
 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
+import static com.android.systemui.classifier.Classifier.LOCK_ICON;
 import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
 
 import android.graphics.Point;
@@ -89,7 +90,9 @@
     Result calculateFalsingResult(
             @Classifier.InteractionType int interactionType,
             double historyBelief, double historyConfidence) {
-        if (interactionType == BRIGHTNESS_SLIDER || interactionType == SHADE_DRAG) {
+        if (interactionType == BRIGHTNESS_SLIDER
+                || interactionType == SHADE_DRAG
+                || interactionType == LOCK_ICON) {
             return Result.passed(0);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 4104e31..46a03e8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -16,6 +16,10 @@
 
 package com.android.systemui.controls.ui
 
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
 import android.os.Bundle
 import android.view.View
 import android.view.ViewGroup
@@ -23,18 +27,25 @@
 import android.view.WindowInsets.Type
 
 import com.android.systemui.R
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.management.ControlsAnimations
 import com.android.systemui.util.LifecycleActivity
 import javax.inject.Inject
 
 /**
- * Displays Device Controls inside an activity
+ * Displays Device Controls inside an activity. This activity is available to be displayed over the
+ * lockscreen if the user has allowed it via
+ * [android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS]. This activity will be
+ * destroyed on SCREEN_OFF events, due to issues with occluded activities over lockscreen as well as
+ * user expectations for the activity to not continue running.
  */
 class ControlsActivity @Inject constructor(
-    private val uiController: ControlsUiController
+    private val uiController: ControlsUiController,
+    private val broadcastDispatcher: BroadcastDispatcher
 ) : LifecycleActivity() {
 
     private lateinit var parent: ViewGroup
+    private lateinit var broadcastReceiver: BroadcastReceiver
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -62,6 +73,8 @@
                 WindowInsets.CONSUMED
             }
         }
+
+        initBroadcastReceiver()
     }
 
     override fun onResume() {
@@ -83,4 +96,25 @@
 
         uiController.hide()
     }
+
+    override fun onDestroy() {
+        super.onDestroy()
+
+        broadcastDispatcher.unregisterReceiver(broadcastReceiver)
+    }
+
+    private fun initBroadcastReceiver() {
+        broadcastReceiver = object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                val action = intent.getAction()
+                if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                    finish()
+                }
+            }
+        }
+
+        val filter = IntentFilter()
+        filter.addAction(Intent.ACTION_SCREEN_OFF)
+        broadcastDispatcher.registerReceiver(broadcastReceiver, filter)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index c97a30e..79ee6a8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -198,9 +198,11 @@
     @SysUISingleton
     @Provides
     static ThemeOverlayApplier provideThemeOverlayManager(Context context,
-            @Background Executor bgExecutor, OverlayManager overlayManager,
+            @Background Executor bgExecutor,
+            @Main Executor mainExecutor,
+            OverlayManager overlayManager,
             DumpManager dumpManager) {
-        return new ThemeOverlayApplier(overlayManager, bgExecutor,
+        return new ThemeOverlayApplier(overlayManager, bgExecutor, mainExecutor,
                 context.getString(R.string.launcher_overlayable_package),
                 context.getString(R.string.themepicker_overlayable_package), dumpManager);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 4196465..657d924 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -214,6 +214,14 @@
     }
 
     /**
+     * Appends display state delayed by UDFPS event to the logs
+     * @param delayedDisplayState the display screen state that was delayed
+     */
+    public void traceDisplayStateDelayedByUdfps(int delayedDisplayState) {
+        mLogger.logDisplayStateDelayedByUdfps(delayedDisplayState);
+    }
+
+    /**
      * Appends display state changed event to the logs
      * @param displayState new DozeMachine state
      */
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 9bc74be..fe37d49 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -160,6 +160,14 @@
         })
     }
 
+    fun logDisplayStateDelayedByUdfps(delayedDisplayState: Int) {
+        buffer.log(TAG, INFO, {
+            str1 = Display.stateToString(delayedDisplayState)
+        }, {
+            "Delaying display state change to: $str1 due to UDFPS activity"
+        })
+    }
+
     fun logDisplayStateChanged(displayState: Int) {
         buffer.log(TAG, INFO, {
             str1 = Display.stateToString(displayState)
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 470d2f3..da7b389 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -29,8 +29,8 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.view.Display;
 
+import com.android.systemui.dock.DockManager;
 import com.android.systemui.doze.dagger.BrightnessSensor;
 import com.android.systemui.doze.dagger.DozeScope;
 import com.android.systemui.doze.dagger.WrappedService;
@@ -63,6 +63,7 @@
     private final Optional<Sensor> mLightSensorOptional;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final DozeParameters mDozeParameters;
+    private final DockManager mDockManager;
     private final int[] mSensorToBrightness;
     private final int[] mSensorToScrimOpacity;
     private final int mScreenBrightnessDim;
@@ -87,7 +88,8 @@
             @BrightnessSensor Optional<Sensor> lightSensorOptional, DozeHost host, Handler handler,
             AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
             WakefulnessLifecycle wakefulnessLifecycle,
-            DozeParameters dozeParameters) {
+            DozeParameters dozeParameters,
+            DockManager dockManager) {
         mContext = context;
         mDozeService = service;
         mSensorManager = sensorManager;
@@ -96,6 +98,7 @@
         mDozeParameters = dozeParameters;
         mDozeHost = host;
         mHandler = handler;
+        mDockManager = dockManager;
 
         mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness;
         mScreenBrightnessDim = alwaysOnDisplayPolicy.dimBrightness;
@@ -107,7 +110,15 @@
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         switch (newState) {
             case INITIALIZED:
+                resetBrightnessToDefault();
+                break;
+            case DOZE_AOD:
+            case DOZE_REQUEST_PULSE:
+            case DOZE_AOD_DOCKED:
+                setLightSensorEnabled(true);
+                break;
             case DOZE:
+                setLightSensorEnabled(false);
                 resetBrightnessToDefault();
                 break;
             case FINISH:
@@ -120,15 +131,6 @@
         }
     }
 
-    @Override
-    public void onScreenState(int state) {
-        if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) {
-            setLightSensorEnabled(true);
-        } else {
-            setLightSensorEnabled(false);
-        }
-    }
-
     private void onDestroy() {
         setLightSensorEnabled(false);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 8c50a16..8f1486b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -26,6 +26,11 @@
 import android.util.Log;
 import android.view.Display;
 
+import androidx.annotation.Nullable;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.UdfpsController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.dagger.DozeScope;
 import com.android.systemui.doze.dagger.WrappedService;
@@ -34,6 +39,7 @@
 import com.android.systemui.util.wakelock.WakeLock;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * Controls the screen when dozing.
@@ -56,23 +62,61 @@
      */
     public static final int ENTER_DOZE_HIDE_WALLPAPER_DELAY = 2500;
 
+    /**
+     * Add an extra delay to the transition to DOZE when udfps is current activated before
+     * the display state transitions from ON => DOZE.
+     */
+    public static final int UDFPS_DISPLAY_STATE_DELAY = 1200;
+
     private final DozeMachine.Service mDozeService;
     private final Handler mHandler;
     private final Runnable mApplyPendingScreenState = this::applyPendingScreenState;
     private final DozeParameters mParameters;
     private final DozeHost mDozeHost;
+    private final AuthController mAuthController;
+    private final Provider<UdfpsController> mUdfpsControllerProvider;
+    @Nullable private UdfpsController mUdfpsController;
+    private final DozeLog mDozeLog;
 
     private int mPendingScreenState = Display.STATE_UNKNOWN;
     private SettableWakeLock mWakeLock;
 
     @Inject
-    public DozeScreenState(@WrappedService DozeMachine.Service service, @Main Handler handler,
-            DozeHost host, DozeParameters parameters, WakeLock wakeLock) {
+    public DozeScreenState(
+            @WrappedService DozeMachine.Service service,
+            @Main Handler handler,
+            DozeHost host,
+            DozeParameters parameters,
+            WakeLock wakeLock,
+            AuthController authController,
+            Provider<UdfpsController> udfpsControllerProvider,
+            DozeLog dozeLog) {
         mDozeService = service;
         mHandler = handler;
         mParameters = parameters;
         mDozeHost = host;
         mWakeLock = new SettableWakeLock(wakeLock, TAG);
+        mAuthController = authController;
+        mUdfpsControllerProvider = udfpsControllerProvider;
+        mDozeLog = dozeLog;
+
+        updateUdfpsController();
+        if (mUdfpsController == null) {
+            mAuthController.addCallback(new AuthController.Callback() {
+                @Override
+                public void onAllAuthenticatorsRegistered() {
+                    updateUdfpsController();
+                }
+            });
+        }
+    }
+
+    private void updateUdfpsController() {
+        if (mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
+            mUdfpsController = mUdfpsControllerProvider.get();
+        } else {
+            mUdfpsController = null;
+        }
     }
 
     @Override
@@ -110,21 +154,28 @@
             mPendingScreenState = screenState;
 
             // Delay screen state transitions even longer while animations are running.
-            boolean shouldDelayTransition = newState == DOZE_AOD
+            boolean shouldDelayTransitionEnteringDoze = newState == DOZE_AOD
                     && mParameters.shouldControlScreenOff() && !turningOn;
 
-            if (shouldDelayTransition) {
+            // Delay screen state transition longer if UDFPS is actively authenticating a fp
+            boolean shouldDelayTransitionForUDFPS = newState == DOZE_AOD
+                    && mUdfpsController != null && mUdfpsController.isFingerDown();
+
+            if (shouldDelayTransitionEnteringDoze || shouldDelayTransitionForUDFPS) {
                 mWakeLock.setAcquired(true);
             }
 
             if (!messagePending) {
                 if (DEBUG) {
                     Log.d(TAG, "Display state changed to " + screenState + " delayed by "
-                            + (shouldDelayTransition ? ENTER_DOZE_DELAY : 1));
+                            + (shouldDelayTransitionEnteringDoze ? ENTER_DOZE_DELAY : 1));
                 }
 
-                if (shouldDelayTransition) {
+                if (shouldDelayTransitionEnteringDoze) {
                     mHandler.postDelayed(mApplyPendingScreenState, ENTER_DOZE_DELAY);
+                } else if (shouldDelayTransitionForUDFPS) {
+                    mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState);
+                    mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY);
                 } else {
                     mHandler.post(mApplyPendingScreenState);
                 }
@@ -139,6 +190,12 @@
     }
 
     private void applyPendingScreenState() {
+        if (mUdfpsController != null && mUdfpsController.isFingerDown()) {
+            mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState);
+            mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY);
+            return;
+        }
+
         applyScreenState(mPendingScreenState);
         mPendingScreenState = Display.STATE_UNKNOWN;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index b2db86f..55e6154 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -456,13 +456,24 @@
 
         public void updateListening() {
             if (!mConfigured || mSensor == null) return;
-            if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)
-                    && !mRegistered) {
-                mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
-                if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered);
+            if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)) {
+                if (!mRegistered) {
+                    mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
+                    if (DEBUG) {
+                        Log.d(TAG, "requestTriggerSensor[" + mSensor
+                                + "] " + mRegistered);
+                    }
+                } else {
+                    if (DEBUG) {
+                        Log.d(TAG, "requestTriggerSensor[" + mSensor
+                                + "] already registered");
+                    }
+                }
             } else if (mRegistered) {
                 final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor);
-                if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt);
+                if (DEBUG) {
+                    Log.d(TAG, "cancelTriggerSensor[" + mSensor + "] " + rt);
+                }
                 mRegistered = false;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index bfa4780..5b327bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -34,7 +34,7 @@
  * See [DumpHandler] for more information on how and when this information is dumped.
  */
 @SysUISingleton
-class DumpManager @Inject constructor() {
+open class DumpManager @Inject constructor() {
     private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
     private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 28f63b0..6561bd5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -25,8 +25,12 @@
  * Proxy to make {@link SystemProperties} easily testable.
  */
 @SysUISingleton
-class SystemPropertiesHelper @Inject constructor() {
+open class SystemPropertiesHelper @Inject constructor() {
     fun getBoolean(name: String, default: Boolean): Boolean {
         return SystemProperties.getBoolean(name, default)
     }
+
+    fun set(name: String, value: Int) {
+        SystemProperties.set(name, value.toString())
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index bc4ced4..1b4a47e 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -62,6 +62,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.view.RotationPolicy;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -172,7 +173,8 @@
             SysUiState sysUiState,
             @Main Handler handler,
             PackageManager packageManager,
-            StatusBar statusBar) {
+            StatusBar statusBar,
+            KeyguardUpdateMonitor keyguardUpdateMonitor) {
 
         super(context,
                 windowManagerFuncs,
@@ -204,7 +206,8 @@
                 sysUiState,
                 handler,
                 packageManager,
-                statusBar);
+                statusBar,
+                keyguardUpdateMonitor);
 
         mLockPatternUtils = lockPatternUtils;
         mKeyguardStateController = keyguardStateController;
@@ -266,7 +269,7 @@
                 this::getWalletViewController, mSysuiColorExtractor,
                 mStatusBarService, mNotificationShadeWindowController,
                 mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger(),
-                getStatusBar());
+                getStatusBar(), getKeyguardUpdateMonitor(), mLockPatternUtils);
 
         if (shouldShowLockMessage(dialog)) {
             dialog.showLockMessage();
@@ -334,12 +337,13 @@
                 NotificationShadeWindowController notificationShadeWindowController,
                 SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
                 MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
-                StatusBar statusBar) {
+                StatusBar statusBar, KeyguardUpdateMonitor keyguardUpdateMonitor,
+                LockPatternUtils lockPatternUtils) {
             super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions,
                     adapter, overflowAdapter, sysuiColorExtractor, statusBarService,
                     notificationShadeWindowController, sysuiState, onRotateCallback,
                     keyguardShowing, powerAdapter, uiEventLogger, null,
-                    statusBar);
+                    statusBar, keyguardUpdateMonitor, lockPatternUtils);
             mWalletFactory = walletFactory;
 
             // Update window attributes
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 06e7482..9ada54b 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -84,6 +84,7 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.BaseAdapter;
 import android.widget.ImageView;
 import android.widget.ImageView.ScaleType;
@@ -108,6 +109,7 @@
 import com.android.internal.util.EmergencyAffordanceManager;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.MultiListLayout;
 import com.android.systemui.MultiListLayout.MultiListAdapter;
 import com.android.systemui.animation.Interpolators;
@@ -170,6 +172,11 @@
     static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
     static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
 
+    // See NotificationManagerService#scheduleDurationReachedLocked
+    private static final long TOAST_FADE_TIME = 333;
+    // See NotificationManagerService.LONG_DELAY
+    private static final int TOAST_VISIBLE_TIME = 3500;
+
     private final Context mContext;
     private final GlobalActionsManager mWindowManagerFuncs;
     private final AudioManager mAudioManager;
@@ -231,6 +238,7 @@
     protected Handler mMainHandler;
     private int mSmallestScreenWidthDp;
     private final StatusBar mStatusBar;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     @VisibleForTesting
     public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
@@ -338,7 +346,8 @@
             SysUiState sysUiState,
             @Main Handler handler,
             PackageManager packageManager,
-            StatusBar statusBar) {
+            StatusBar statusBar,
+            KeyguardUpdateMonitor keyguardUpdateMonitor) {
         mContext = context;
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
@@ -369,6 +378,7 @@
         mMainHandler = handler;
         mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp;
         mStatusBar = statusBar;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -422,6 +432,10 @@
         return mStatusBar;
     }
 
+    protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() {
+        return mKeyguardUpdateMonitor;
+    }
+
     /**
      * Show the global actions dialog (creating if necessary)
      *
@@ -653,7 +667,7 @@
                 mAdapter, mOverflowAdapter, mSysuiColorExtractor,
                 mStatusBarService, mNotificationShadeWindowController,
                 mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
-                mInfoProvider, mStatusBar);
+                mInfoProvider, mStatusBar, mKeyguardUpdateMonitor, mLockPatternUtils);
 
         dialog.setOnDismissListener(this);
         dialog.setOnShowListener(this);
@@ -2122,6 +2136,8 @@
         private GlobalActionsInfoProvider mInfoProvider;
         private GestureDetector mGestureDetector;
         private StatusBar mStatusBar;
+        private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+        private LockPatternUtils mLockPatternUtils;
 
         protected ViewGroup mContainer;
 
@@ -2173,7 +2189,8 @@
                 NotificationShadeWindowController notificationShadeWindowController,
                 SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
                 MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
-                @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar) {
+                @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar,
+                KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) {
             super(context, themeRes);
             mContext = context;
             mAdapter = adapter;
@@ -2188,6 +2205,8 @@
             mUiEventLogger = uiEventLogger;
             mInfoProvider = infoProvider;
             mStatusBar = statusBar;
+            mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+            mLockPatternUtils = lockPatternUtils;
 
             mGestureDetector = new GestureDetector(mContext, mGestureListener);
 
@@ -2308,6 +2327,14 @@
             if (mInfoProvider != null && mInfoProvider.shouldShowMessage()) {
                 mInfoProvider.addPanel(mContext, mContainer, mAdapter.getCount(), () -> dismiss());
             }
+
+            // If user entered from the lock screen and smart lock was enabled, disable it
+            int user = KeyguardUpdateMonitor.getCurrentUser();
+            boolean userHasTrust = mKeyguardUpdateMonitor.getUserHasTrust(user);
+            if (mKeyguardShowing && userHasTrust) {
+                mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
+                showSmartLockDisabledMessage();
+            }
         }
 
         protected void fixNavBarClipping() {
@@ -2319,6 +2346,37 @@
             contentParent.setClipToPadding(false);
         }
 
+        private void showSmartLockDisabledMessage() {
+            // Since power menu is the top window, make a Toast-like view that will show up
+            View message = LayoutInflater.from(mContext)
+                    .inflate(com.android.systemui.R.layout.global_actions_toast, mContainer, false);
+
+            // Set up animation
+            AccessibilityManager mAccessibilityManager =
+                    (AccessibilityManager) getContext().getSystemService(
+                            Context.ACCESSIBILITY_SERVICE);
+            final int visibleTime = mAccessibilityManager.getRecommendedTimeoutMillis(
+                    TOAST_VISIBLE_TIME, AccessibilityManager.FLAG_CONTENT_TEXT);
+            message.setVisibility(View.VISIBLE);
+            message.setAlpha(0f);
+            mContainer.addView(message);
+
+            // Fade in
+            message.animate()
+                    .alpha(1f)
+                    .setDuration(TOAST_FADE_TIME)
+                    .setListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            // Then fade out
+                            message.animate()
+                                    .alpha(0f)
+                                    .setDuration(TOAST_FADE_TIME)
+                                    .setStartDelay(visibleTime);
+                        }
+                    });
+        }
+
         @Override
         protected void onStart() {
             super.onStart();
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index d30783c..a51ec54 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -102,7 +102,6 @@
 
     @Override
     public Size reportSurfaceSize() {
-        mTexture.use(null /* consumer */);
         mSurfaceSize.set(mTexture.getTextureDimensions());
         return new Size(mSurfaceSize.width(), mSurfaceSize.height());
     }
@@ -124,6 +123,7 @@
         private final WallpaperManager mWallpaperManager;
         private Bitmap mBitmap;
         private boolean mWcgContent;
+        private boolean mTextureUsed;
 
         private WallpaperTexture(WallpaperManager wallpaperManager) {
             mWallpaperManager = wallpaperManager;
@@ -141,6 +141,7 @@
                     mWallpaperManager.forgetLoadedWallpaper();
                     if (mBitmap != null) {
                         mDimensions.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+                        mTextureUsed = true;
                     } else {
                         Log.w(TAG, "Can't get bitmap");
                     }
@@ -171,6 +172,9 @@
         }
 
         private Rect getTextureDimensions() {
+            if (!mTextureUsed) {
+                mDimensions.set(mWallpaperManager.peekBitmapDimensions());
+            }
             return mDimensions;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 2facf3d..db5dbb0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -212,14 +212,32 @@
                 isSsReactivated: Boolean
             ) {
                 if (addOrUpdatePlayer(key, oldKey, data)) {
+                    // Log card received if a new resumable media card is added
                     MediaPlayerData.getMediaPlayer(key)?.let {
                         logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
                                 it.mInstanceId,
+                                it.mUid,
                                 /* isRecommendationCard */ false,
                                 it.surfaceForSmartspaceLogging,
                                 rank = MediaPlayerData.getMediaPlayerIndex(key))
                     }
                 }
+                if (isSsReactivated) {
+                    // If resumable media is reactivated by headphone connection, update instance
+                    // id for each card and log a receive event.
+                    MediaPlayerData.players().forEachIndexed { index, it ->
+                        if (it.recommendationViewHolder == null) {
+                            it.mInstanceId = SmallHash.hash(it.mUid +
+                                    systemClock.currentTimeMillis().toInt())
+                            logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
+                                    it.mInstanceId,
+                                    it.mUid,
+                                    /* isRecommendationCard */ false,
+                                    it.surfaceForSmartspaceLogging,
+                                    rank = index)
+                        }
+                    }
+                }
                 if (mediaCarouselScrollHandler.visibleToUser &&
                         isSsReactivated && !mediaCarouselScrollHandler.qsExpanded) {
                     // It could happen that reactived media player isn't visible to user because
@@ -252,6 +270,7 @@
                     MediaPlayerData.getMediaPlayer(key)?.let {
                         logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
                                 it.mInstanceId,
+                                it.mUid,
                                 /* isRecommendationCard */ true,
                                 it.surfaceForSmartspaceLogging,
                                 rank = MediaPlayerData.getMediaPlayerIndex(key))
@@ -261,6 +280,7 @@
                                 MediaPlayerData.getMediaPlayerIndex(key)) {
                             logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
                                     it.mInstanceId,
+                                    it.mUid,
                                     /* isRecommendationCard */ true,
                                     it.surfaceForSmartspaceLogging)
                         }
@@ -339,9 +359,9 @@
             if (activeMediaIndex != -1) {
                 previousVisiblePlayerKey?.let {
                     val previousVisibleIndex = MediaPlayerData.playerKeys()
-                        .indexOfFirst { key -> it == key }
+                            .indexOfFirst { key -> it == key }
                     mediaCarouselScrollHandler
-                        .scrollToPlayer(previousVisibleIndex, activeMediaIndex)
+                            .scrollToPlayer(previousVisibleIndex, activeMediaIndex)
                 } ?: {
                     mediaCarouselScrollHandler.scrollToPlayer(destIndex = activeMediaIndex)
                 }
@@ -355,11 +375,11 @@
         MediaPlayerData.moveIfExists(oldKey, key)
         val existingPlayer = MediaPlayerData.getMediaPlayer(key)
         val curVisibleMediaKey = MediaPlayerData.playerKeys()
-            .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+                .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
         if (existingPlayer == null) {
             var newPlayer = mediaControlPanelFactory.get()
             newPlayer.attachPlayer(
-                PlayerViewHolder.create(LayoutInflater.from(context), mediaContent))
+                    PlayerViewHolder.create(LayoutInflater.from(context), mediaContent))
             newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
             val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.WRAP_CONTENT)
@@ -407,14 +427,14 @@
 
         var newRecs = mediaControlPanelFactory.get()
         newRecs.attachRecommendation(
-            RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent))
+                RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent))
         newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
         val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-            ViewGroup.LayoutParams.WRAP_CONTENT)
+                ViewGroup.LayoutParams.WRAP_CONTENT)
         newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
         newRecs.bindRecommendation(data.copy(backgroundColor = bgColor))
         val curVisibleMediaKey = MediaPlayerData.playerKeys()
-            .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+                .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
         MediaPlayerData.addMediaRecommendation(key, data, newRecs, shouldPrioritize, systemClock)
         updatePlayerToState(newRecs, noAnimation = true)
         reorderAllPlayers(curVisibleMediaKey)
@@ -462,7 +482,7 @@
                 removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
                 smartspaceMediaData?.let {
                     addSmartspaceMediaRecommendations(
-                        it.targetId, it, MediaPlayerData.shouldPrioritizeSs)
+                            it.targetId, it, MediaPlayerData.shouldPrioritizeSs)
                 }
             } else {
                 removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
@@ -585,7 +605,7 @@
                 ?: endShowsActive
         if (currentlyShowingOnlyActive != endShowsActive ||
                 ((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) &&
-                            startShowsActive != endShowsActive)) {
+                        startShowsActive != endShowsActive)) {
             // Whenever we're transitioning from between differing states or the endstate differs
             // we reset the translation
             currentlyShowingOnlyActive = endShowsActive
@@ -696,23 +716,43 @@
             }
             logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
                     mediaControlPanel.mInstanceId,
+                    mediaControlPanel.mUid,
                     isRecommendationCard,
                     mediaControlPanel.surfaceForSmartspaceLogging)
         }
     }
 
     @JvmOverloads
+    /**
+     * Log Smartspace events
+     *
+     * @param eventId UI event id (e.g. 800 for SMARTSPACE_CARD_SEEN)
+     * @param instanceId id to uniquely identify a card, e.g. each headphone generates a new
+     * instanceId
+     * @param uid uid for the application that media comes from
+     * @param isRecommendationCard whether the card is media recommendation
+     * @param surface which display surface the media card is on (e.g. lockscreen, shade)
+     * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1
+     * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc.
+     * @param interactedSubcardCardinality how many media items were shown to the user when there
+     * is user interaction
+     * @param rank the rank for media card in the media carousel, starting from 0
+     *
+     */
     fun logSmartspaceCardReported(
         eventId: Int,
         instanceId: Int,
+        uid: Int,
         isRecommendationCard: Boolean,
         surface: Int,
+        interactedSubcardRank: Int = 0,
+        interactedSubcardCardinality: Int = 0,
         rank: Int = mediaCarouselScrollHandler.visibleMediaIndex
     ) {
         // Only log media resume card when Smartspace data is available
         if (!isRecommendationCard &&
-                        !mediaManager.smartspaceMediaData.isActive &&
-                                MediaPlayerData.smartspaceMediaData == null) {
+                !mediaManager.smartspaceMediaData.isActive &&
+                MediaPlayerData.smartspaceMediaData == null) {
             return
         }
 
@@ -720,13 +760,20 @@
         SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
                 eventId,
                 instanceId,
-                if (isRecommendationCard)
-                    SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS
-                else
-                    SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_RESUME_MEDIA,
+                // Deprecated, replaced with AiAi feature type so we don't need to create logging
+                // card type for each new feature.
+                SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
                 surface,
                 rank,
-                mediaContent.getChildCount())
+                mediaContent.getChildCount(),
+                if (isRecommendationCard)
+                    15 // MEDIA_RECOMMENDATION
+                else
+                    31, // MEDIA_RESUME
+                uid,
+                interactedSubcardRank,
+                interactedSubcardCardinality
+        )
         /* ktlint-disable max-line-length */
     }
 
@@ -738,18 +785,20 @@
         if (!recommendation.isEmpty()) {
             logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
                     recommendation.get(0).mInstanceId,
+                    recommendation.get(0).mUid,
                     true,
                     recommendation.get(0).surfaceForSmartspaceLogging,
-            /* rank */-1)
+                    rank = -1)
         } else {
             val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
             if (MediaPlayerData.players().size > visibleMediaIndex) {
                 val player = MediaPlayerData.players().elementAt(visibleMediaIndex)
                 logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
                         player.mInstanceId,
-                false,
+                        player.mUid,
+                        false,
                         player.surfaceForSmartspaceLogging,
-                /* rank */-1)
+                        rank = -1)
             }
         }
         mediaManager.onSwipeToDismiss()
@@ -768,7 +817,7 @@
 @VisibleForTesting
 internal object MediaPlayerData {
     private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null,
-        emptyList(), emptyList(), "INVALID", null, null, null, true, null)
+            emptyList(), emptyList(), "INVALID", null, null, null, true, null)
     // Whether should prioritize Smartspace card.
     internal var shouldPrioritizeSs: Boolean = false
         private set
@@ -776,18 +825,18 @@
         private set
 
     data class MediaSortKey(
-        // Whether the item represents a Smartspace media recommendation.
+            // Whether the item represents a Smartspace media recommendation.
         val isSsMediaRec: Boolean,
         val data: MediaData,
         val updateTime: Long = 0
     )
 
     private val comparator =
-        compareByDescending<MediaSortKey> { it.data.isPlaying }
-            .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
-            .thenByDescending { it.data.isLocalSession }
-            .thenByDescending { !it.data.resumption }
-            .thenByDescending { it.updateTime }
+            compareByDescending<MediaSortKey> { it.data.isPlaying }
+                    .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
+                    .thenByDescending { it.data.isLocalSession }
+                    .thenByDescending { !it.data.resumption }
+                    .thenByDescending { it.updateTime }
 
     private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
     private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
@@ -888,4 +937,4 @@
         }
         return false
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 15a7083..c125612 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -33,6 +33,7 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
+import android.os.Process;
 import android.text.Layout;
 import android.util.Log;
 import android.view.View;
@@ -111,6 +112,9 @@
     private int mAlbumArtSize;
     // Instance id for logging purpose.
     protected int mInstanceId = -1;
+    // Uid for the media app.
+    protected int mUid = Process.INVALID_UID;
+    private int mSmartspaceMediaItemsCount;
     private MediaCarouselController mMediaCarouselController;
     private final MediaOutputDialogFactory mMediaOutputDialogFactory;
 
@@ -266,7 +270,13 @@
         }
         mKey = key;
         MediaSession.Token token = data.getToken();
-        mInstanceId = SmallHash.hash(data.getPackageName());
+        PackageManager packageManager = mContext.getPackageManager();
+        try {
+            mUid = packageManager.getApplicationInfo(data.getPackageName(), 0 /* flags */).uid;
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Unable to look up package name", e);
+        }
+        mInstanceId = SmallHash.hash(mUid);
 
         mBackgroundColor = data.getBackgroundColor();
         if (mToken == null || !mToken.equals(token)) {
@@ -360,27 +370,16 @@
 
         final MediaDeviceData device = data.getDevice();
         final int seamlessId = mPlayerViewHolder.getSeamless().getId();
-        final int seamlessFallbackId = mPlayerViewHolder.getSeamlessFallback().getId();
-        final boolean showFallback = device != null && !device.getEnabled();
-        final int seamlessFallbackVisibility = showFallback ? View.VISIBLE : View.GONE;
-        mPlayerViewHolder.getSeamlessFallback().setVisibility(seamlessFallbackVisibility);
-        expandedSet.setVisibility(seamlessFallbackId, seamlessFallbackVisibility);
-        collapsedSet.setVisibility(seamlessFallbackId, seamlessFallbackVisibility);
-        final int seamlessVisibility = showFallback ? View.GONE : View.VISIBLE;
-        mPlayerViewHolder.getSeamless().setVisibility(seamlessVisibility);
-        expandedSet.setVisibility(seamlessId, seamlessVisibility);
-        collapsedSet.setVisibility(seamlessId, seamlessVisibility);
-        final float seamlessAlpha = data.getResumption() ? DISABLED_ALPHA : 1.0f;
+        // Disable clicking on output switcher for invalid devices and resumption controls
+        final boolean seamlessDisabled = (device != null && !device.getEnabled())
+                || data.getResumption();
+        final float seamlessAlpha = seamlessDisabled ? DISABLED_ALPHA : 1.0f;
         expandedSet.setAlpha(seamlessId, seamlessAlpha);
         collapsedSet.setAlpha(seamlessId, seamlessAlpha);
-        // Disable clicking on output switcher for resumption controls.
-        mPlayerViewHolder.getSeamless().setEnabled(!data.getResumption());
+        mPlayerViewHolder.getSeamless().setEnabled(!seamlessDisabled);
         String deviceString = null;
-        if (showFallback) {
-            iconView.setImageDrawable(null);
-        } else if (device != null) {
+        if (device != null && device.getEnabled()) {
             Drawable icon = device.getIcon();
-            iconView.setVisibility(View.VISIBLE);
             if (icon instanceof AdaptiveIcon) {
                 AdaptiveIcon aIcon = (AdaptiveIcon) icon;
                 aIcon.setBackgroundColor(mBackgroundColor);
@@ -391,10 +390,9 @@
             deviceString = device.getName();
         } else {
             // Reset to default
-            Log.w(TAG, "device is null. Not binding output chip.");
-            iconView.setVisibility(View.GONE);
-            deviceString = mContext.getString(
-                    com.android.internal.R.string.ext_media_seamless_action);
+            Log.w(TAG, "Device is null or not enabled: " + device + ", not binding output chip.");
+            iconView.setImageResource(R.drawable.ic_media_home_devices);
+            deviceString =  mContext.getString(R.string.media_seamless_other_device);
         }
         deviceName.setText(deviceString);
         seamlessView.setContentDescription(deviceString);
@@ -531,10 +529,11 @@
         }
 
         // Set up recommendation card's header.
-        ApplicationInfo applicationInfo = null;
+        ApplicationInfo applicationInfo;
         try {
             applicationInfo = mContext.getPackageManager()
                     .getApplicationInfo(data.getPackageName(), 0 /* flags */);
+            mUid = applicationInfo.uid;
         } catch (PackageManager.NameNotFoundException e) {
             Log.w(TAG, "Fail to get media recommendation's app info", e);
             return;
@@ -553,7 +552,8 @@
             headerTitleText.setText(appLabel);
         }
         // Set up media rec card's tap action if applicable.
-        setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction());
+        setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(),
+                /* interactedSubcardRank */ -1);
         // Set up media rec card's accessibility label.
         recommendationCard.setContentDescription(
                 mContext.getString(R.string.controls_media_smartspace_rec_description, appLabel));
@@ -567,7 +567,8 @@
         ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
         int mediaRecommendationNum = Math.min(mediaRecommendationList.size(),
                 MEDIA_RECOMMENDATION_MAX_NUM);
-        for (int itemIndex = 0, uiComponentIndex = 0;
+        int uiComponentIndex = 0;
+        for (int itemIndex = 0;
                 itemIndex < mediaRecommendationNum && uiComponentIndex < mediaRecommendationNum;
                 itemIndex++) {
             SmartspaceAction recommendation = mediaRecommendationList.get(itemIndex);
@@ -582,7 +583,8 @@
 
             // Set up the media item's click listener if applicable.
             ViewGroup mediaCoverContainer = mediaCoverContainers.get(uiComponentIndex);
-            setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation);
+            setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation,
+                    uiComponentIndex);
 
             // Set up the accessibility label for the media item.
             String artistName = recommendation.getExtras()
@@ -614,10 +616,10 @@
                     mediaCoverItemsResIds.get(uiComponentIndex), true);
             setVisibleAndAlpha(expandedSet,
                     mediaCoverContainersResIds.get(uiComponentIndex), true);
-
             uiComponentIndex++;
         }
 
+        mSmartspaceMediaItemsCount = uiComponentIndex;
         // Set up long press to show guts setting panel.
         mRecommendationViewHolder.getDismiss().setOnClickListener(v -> {
             logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
@@ -750,7 +752,8 @@
 
     private void setSmartspaceRecItemOnClickListener(
             @NonNull View view,
-            @NonNull SmartspaceAction action) {
+            @NonNull SmartspaceAction action,
+            int interactedSubcardRank) {
         if (view == null || action == null || action.getIntent() == null
                 || action.getIntent().getExtras() == null) {
             Log.e(TAG, "No tap action can be set up");
@@ -758,9 +761,10 @@
         }
 
         view.setOnClickListener(v -> {
-            // When media recommendation card is shown, it will always be the top card.
             logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
-                    /* isRecommendationCard */ true);
+                    /* isRecommendationCard */ true,
+                    interactedSubcardRank,
+                    getSmartspaceSubCardCardinality());
 
             if (shouldSmartspaceRecItemOpenInForeground(action)) {
                 // Request to unlock the device if the activity needs to be opened in foreground.
@@ -818,9 +822,28 @@
     }
 
     private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard) {
+        logSmartspaceCardReported(eventId, isRecommendationCard,
+                /* interactedSubcardRank */ 0,
+                /* interactedSubcardCardinality */ 0);
+    }
+
+    private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard,
+            int interactedSubcardRank, int interactedSubcardCardinality) {
         mMediaCarouselController.logSmartspaceCardReported(eventId,
                 mInstanceId,
+                mUid,
                 isRecommendationCard,
-                getSurfaceForSmartspaceLogging());
+                getSurfaceForSmartspaceLogging(),
+                interactedSubcardRank,
+                interactedSubcardCardinality);
     }
-}
+
+    private int getSmartspaceSubCardCardinality() {
+        if (!mMediaCarouselController.getMediaCarouselScrollHandler().getQsExpanded()
+                && mSmartspaceMediaItemsCount > 3) {
+            return 3;
+        }
+
+        return mSmartspaceMediaItemsCount;
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 0a28b47..ba99f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -212,8 +212,8 @@
         mediaDataCombineLatest.addListener(mediaDataFilter)
 
         // Set up links back into the pipeline for listeners that need to send events upstream.
-        mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
-            setTimedOut(token, timedOut) }
+        mediaTimeoutListener.timeoutCallback = { key: String, timedOut: Boolean ->
+            setTimedOut(key, timedOut) }
         mediaResumeListener.setManager(this)
         mediaDataFilter.mediaDataManager = this
 
@@ -414,14 +414,18 @@
      * This will make the player not active anymore, hiding it from QQS and Keyguard.
      * @see MediaData.active
      */
-    internal fun setTimedOut(token: String, timedOut: Boolean, forceUpdate: Boolean = false) {
-        mediaEntries[token]?.let {
+    internal fun setTimedOut(key: String, timedOut: Boolean, forceUpdate: Boolean = false) {
+        mediaEntries[key]?.let {
             if (it.active == !timedOut && !forceUpdate) {
+                if (it.resumption) {
+                    if (DEBUG) Log.d(TAG, "timing out resume player $key")
+                    dismissMediaData(key, 0L /* delay */)
+                }
                 return
             }
             it.active = !timedOut
-            if (DEBUG) Log.d(TAG, "Updating $token timedOut: $timedOut")
-            onMediaDataLoaded(token, token, it)
+            if (DEBUG) Log.d(TAG, "Updating $key timedOut: $timedOut")
+            onMediaDataLoaded(key, key, it)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index ab568c8..608c784 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.Utils
+import com.android.systemui.util.time.SystemClock
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.concurrent.ConcurrentLinkedQueue
@@ -53,11 +54,13 @@
     @Background private val backgroundExecutor: Executor,
     private val tunerService: TunerService,
     private val mediaBrowserFactory: ResumeMediaBrowserFactory,
-    dumpManager: DumpManager
+    dumpManager: DumpManager,
+    private val systemClock: SystemClock
 ) : MediaDataManager.Listener, Dumpable {
 
     private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
-    private val resumeComponents: ConcurrentLinkedQueue<ComponentName> = ConcurrentLinkedQueue()
+    private val resumeComponents: ConcurrentLinkedQueue<Pair<ComponentName, Long>> =
+            ConcurrentLinkedQueue()
 
     private lateinit var mediaDataManager: MediaDataManager
 
@@ -131,14 +134,32 @@
         val listString = prefs.getString(MEDIA_PREFERENCE_KEY + currentUserId, null)
         val components = listString?.split(ResumeMediaBrowser.DELIMITER.toRegex())
             ?.dropLastWhile { it.isEmpty() }
+        var needsUpdate = false
         components?.forEach {
             val info = it.split("/")
             val packageName = info[0]
             val className = info[1]
             val component = ComponentName(packageName, className)
-            resumeComponents.add(component)
+
+            val lastPlayed = if (info.size == 3) {
+                try {
+                    info[2].toLong()
+                } catch (e: NumberFormatException) {
+                    needsUpdate = true
+                    systemClock.currentTimeMillis()
+                }
+            } else {
+                needsUpdate = true
+                systemClock.currentTimeMillis()
+            }
+            resumeComponents.add(component to lastPlayed)
         }
         Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}")
+
+        if (needsUpdate) {
+            // Save any missing times that we had to fill in
+            writeSharedPrefs()
+        }
     }
 
     /**
@@ -149,9 +170,12 @@
             return
         }
 
+        val now = systemClock.currentTimeMillis()
         resumeComponents.forEach {
-            val browser = mediaBrowserFactory.create(mediaBrowserCallback, it)
-            browser.findRecentMedia()
+            if (now.minus(it.second) <= RESUME_MEDIA_TIMEOUT) {
+                val browser = mediaBrowserFactory.create(mediaBrowserCallback, it.first)
+                browser.findRecentMedia()
+            }
         }
     }
 
@@ -234,18 +258,24 @@
      */
     private fun updateResumptionList(componentName: ComponentName) {
         // Remove if exists
-        resumeComponents.remove(componentName)
+        resumeComponents.remove(resumeComponents.find { it.first.equals(componentName) })
         // Insert at front of queue
-        resumeComponents.add(componentName)
+        val currentTime = systemClock.currentTimeMillis()
+        resumeComponents.add(componentName to currentTime)
         // Remove old components if over the limit
         if (resumeComponents.size > ResumeMediaBrowser.MAX_RESUMPTION_CONTROLS) {
             resumeComponents.remove()
         }
 
-        // Save changes
+        writeSharedPrefs()
+    }
+
+    private fun writeSharedPrefs() {
         val sb = StringBuilder()
         resumeComponents.forEach {
-            sb.append(it.flattenToString())
+            sb.append(it.first.flattenToString())
+            sb.append("/")
+            sb.append(it.second)
             sb.append(ResumeMediaBrowser.DELIMITER)
         }
         val prefs = context.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 9a39193..6f04771 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -20,6 +20,7 @@
 import android.media.session.PlaybackState
 import android.os.SystemProperties
 import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
@@ -29,9 +30,15 @@
 
 private const val DEBUG = true
 private const val TAG = "MediaTimeout"
-private val PAUSED_MEDIA_TIMEOUT = SystemProperties
+
+@VisibleForTesting
+val PAUSED_MEDIA_TIMEOUT = SystemProperties
         .getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))
 
+@VisibleForTesting
+val RESUME_MEDIA_TIMEOUT = SystemProperties
+        .getLong("debug.sysui.media_timeout_resume", TimeUnit.DAYS.toMillis(3))
+
 /**
  * Controller responsible for keeping track of playback states and expiring inactive streams.
  */
@@ -45,8 +52,9 @@
 
     /**
      * Callback representing that a media object is now expired:
-     * @param token Media session unique identifier
-     * @param pauseTimeout True when expired for {@code PAUSED_MEDIA_TIMEOUT}
+     * @param key Media control unique identifier
+     * @param timedOut True when expired for {@code PAUSED_MEDIA_TIMEOUT} for active media,
+     *                 or {@code RESUME_MEDIA_TIMEOUT} for resume media
      */
     lateinit var timeoutCallback: (String, Boolean) -> Unit
 
@@ -122,6 +130,7 @@
 
         var timedOut = false
         var playing: Boolean? = null
+        var resumption: Boolean? = null
         var destroyed = false
 
         var mediaData: MediaData = data
@@ -159,12 +168,19 @@
         }
 
         override fun onSessionDestroyed() {
-            // If the session is destroyed, the controller is no longer valid, and we will need to
-            // recreate it if this key is updated later
             if (DEBUG) {
                 Log.d(TAG, "Session destroyed for $key")
             }
-            destroy()
+
+            if (resumption == true) {
+                // Some apps create a session when MBS is queried. We should unregister the
+                // controller since it will no longer be valid, but don't cancel the timeout
+                mediaController?.unregisterCallback(this)
+            } else {
+                // For active controls, if the session is destroyed, clean up everything since we
+                // will need to recreate it if this key is updated later
+                destroy()
+            }
         }
 
         private fun processState(state: PlaybackState?, dispatchEvents: Boolean) {
@@ -173,20 +189,28 @@
             }
 
             val isPlaying = state != null && isPlayingState(state.state)
-            if (playing == isPlaying && playing != null) {
+            val resumptionChanged = resumption != mediaData.resumption
+            if (playing == isPlaying && playing != null && !resumptionChanged) {
                 return
             }
             playing = isPlaying
+            resumption = mediaData.resumption
 
             if (!isPlaying) {
                 if (DEBUG) {
-                    Log.v(TAG, "schedule timeout for $key")
+                    Log.v(TAG, "schedule timeout for $key playing $isPlaying, $resumption")
                 }
-                if (cancellation != null) {
+                if (cancellation != null && !resumptionChanged) {
+                    // if the media changed resume state, we'll need to adjust the timeout length
                     if (DEBUG) Log.d(TAG, "cancellation already exists, continuing.")
                     return
                 }
-                expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state")
+                expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state, $resumption")
+                val timeout = if (mediaData.resumption) {
+                    RESUME_MEDIA_TIMEOUT
+                } else {
+                    PAUSED_MEDIA_TIMEOUT
+                }
                 cancellation = mainExecutor.executeDelayed({
                     cancellation = null
                     if (DEBUG) {
@@ -195,7 +219,7 @@
                     timedOut = true
                     // this event is async, so it's safe even when `dispatchEvents` is false
                     timeoutCallback(key, timedOut)
-                }, PAUSED_MEDIA_TIMEOUT)
+                }, timeout)
             } else {
                 expireMediaTimeout(key, "playback started - $state, $key")
                 timedOut = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 791f59d..35603b6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -43,7 +43,6 @@
     val seamless = itemView.requireViewById<ViewGroup>(R.id.media_seamless)
     val seamlessIcon = itemView.requireViewById<ImageView>(R.id.media_seamless_image)
     val seamlessText = itemView.requireViewById<TextView>(R.id.media_seamless_text)
-    val seamlessFallback = itemView.requireViewById<ImageView>(R.id.media_seamless_fallback)
 
     // Seek bar
     val seekBar = itemView.requireViewById<SeekBar>(R.id.media_progress_bar)
@@ -124,7 +123,6 @@
                 R.id.header_title,
                 R.id.header_artist,
                 R.id.media_seamless,
-                R.id.media_seamless_fallback,
                 R.id.notification_media_progress_time,
                 R.id.media_progress_bar,
                 R.id.action0,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index cb11454..5d37bff 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -410,6 +410,12 @@
             new Handler(Looper.getMainLooper())) {
         @Override
         public void onChange(boolean selfChange, Uri uri) {
+            // TODO(b/198002034): Content observers currently can still be called back after being
+            // unregistered, and in this case we can ignore the change if the nav bar has been
+            // destroyed already
+            if (mNavigationBarView == null) {
+                return;
+            }
             updateAssistantEntrypoints();
         }
     };
@@ -651,7 +657,7 @@
         if (mIsOnDefaultDisplay) {
             final RotationButtonController rotationButtonController =
                     mNavigationBarView.getRotationButtonController();
-            rotationButtonController.addRotationCallback(mRotationWatcher);
+            rotationButtonController.setRotationCallback(mRotationWatcher);
 
             // Reset user rotation pref to match that of the WindowManager if starting in locked
             // mode. This will automatically happen when switching from auto-rotate to locked mode.
@@ -690,6 +696,9 @@
 
     @Override
     public void onViewDetachedFromWindow(View v) {
+        final RotationButtonController rotationButtonController =
+                mNavigationBarView.getRotationButtonController();
+        rotationButtonController.setRotationCallback(null);
         mNavigationBarView.getBarTransitions().destroy();
         mNavigationBarView.getLightTransitionsController().destroy(mContext);
         mOverviewProxyService.removeCallback(mOverviewProxyListener);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 61e8033..0433247 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -274,7 +274,7 @@
     };
 
     private final Consumer<Boolean> mRotationButtonListener = (visible) -> {
-        if (visible) {
+        if (visible && mAutoHideController != null) {
             // If the button will actually become visible and the navbar is about to hide,
             // tell the statusbar to keep it around for longer
             mAutoHideController.touchAutoHide();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index 649ac87..8ea9ae3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -180,7 +180,7 @@
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
     }
 
-    void addRotationCallback(Consumer<Integer> watcher) {
+    void setRotationCallback(Consumer<Integer> watcher) {
         mRotWatcherListener = watcher;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
index a626681..d0aa710 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
@@ -29,6 +29,7 @@
 import androidx.annotation.MainThread
 import androidx.annotation.VisibleForTesting
 import androidx.annotation.WorkerThread
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -67,6 +68,7 @@
     private val privacyLogger: PrivacyLogger,
     private val keyguardStateController: KeyguardStateController,
     private val appOpsController: AppOpsController,
+    private val uiEventLogger: UiEventLogger,
     @VisibleForTesting private val dialogProvider: DialogProvider
 ) {
 
@@ -81,7 +83,8 @@
         @Main uiExecutor: Executor,
         privacyLogger: PrivacyLogger,
         keyguardStateController: KeyguardStateController,
-        appOpsController: AppOpsController
+        appOpsController: AppOpsController,
+        uiEventLogger: UiEventLogger
     ) : this(
             permissionManager,
             packageManager,
@@ -93,6 +96,7 @@
             privacyLogger,
             keyguardStateController,
             appOpsController,
+            uiEventLogger,
             defaultDialogProvider
     )
 
@@ -105,6 +109,7 @@
     private val onDialogDismissed = object : PrivacyDialog.OnDialogDismissed {
         override fun onDialogDismissed() {
             privacyLogger.logPrivacyDialogDismissed()
+            uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
             dialog = null
         }
     }
@@ -114,6 +119,8 @@
         val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
         intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
         intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
+        uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+                userId, packageName)
         privacyLogger.logStartSettingsActivityFromDialog(packageName, userId)
         if (!keyguardStateController.isUnlocked) {
             // If we are locked, hide the dialog so the user can unlock
@@ -155,7 +162,7 @@
             val items = usage.mapNotNull {
                 val type = filterType(permGroupToPrivacyType(it.permGroupName))
                 val userInfo = userInfos.firstOrNull { ui -> ui.id == UserHandle.getUserId(it.uid) }
-                userInfo?.let { ui ->
+                if (userInfo != null || it.isPhoneCall) {
                     type?.let { t ->
                         // Only try to get the app name if we actually need it
                         val appName = if (it.isPhoneCall) {
@@ -171,10 +178,14 @@
                                 it.attribution,
                                 it.lastAccess,
                                 it.isActive,
-                                ui.isManagedProfile,
+                                // If there's no user info, we're in a phoneCall in secondary user
+                                userInfo?.isManagedProfile ?: false,
                                 it.isPhoneCall
                         )
                     }
+                } else {
+                    // No matching user or phone call
+                    null
                 }
             }
             uiExecutor.execute {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
new file mode 100644
index 0000000..3ecc5a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 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.privacy
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class PrivacyDialogEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+    @UiEvent(doc = "Privacy dialog is clicked by user to go to the app settings page.")
+    PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS(904),
+
+    @UiEvent(doc = "Privacy dialog is dismissed by user.")
+    PRIVACY_DIALOG_DISMISSED(905);
+
+    override fun getId() = _id
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
deleted file mode 100644
index 38b20ee..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static com.android.systemui.statusbar.phone.AutoTileManager.HOTSPOT;
-import static com.android.systemui.statusbar.phone.AutoTileManager.INVERSION;
-import static com.android.systemui.statusbar.phone.AutoTileManager.NIGHT;
-import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER;
-import static com.android.systemui.statusbar.phone.AutoTileManager.WORK;
-
-import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.ArraySet;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Prefs;
-import com.android.systemui.Prefs.Key;
-import com.android.systemui.util.UserAwareController;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-
-import javax.inject.Inject;
-
-public class AutoAddTracker implements UserAwareController {
-
-    private static final String[][] CONVERT_PREFS = {
-            {Key.QS_HOTSPOT_ADDED, HOTSPOT},
-            {Key.QS_DATA_SAVER_ADDED, SAVER},
-            {Key.QS_INVERT_COLORS_ADDED, INVERSION},
-            {Key.QS_WORK_ADDED, WORK},
-            {Key.QS_NIGHTDISPLAY_ADDED, NIGHT},
-    };
-
-    private final ArraySet<String> mAutoAdded;
-    private final Context mContext;
-    private int mUserId;
-
-    public AutoAddTracker(Context context, int userId) {
-        mContext = context;
-        mUserId = userId;
-        mAutoAdded = new ArraySet<>(getAdded());
-    }
-
-    /**
-     * Init method must be called after construction to start listening
-     */
-    public void initialize() {
-        // TODO: remove migration code and shared preferences keys after P release
-        if (mUserId == UserHandle.USER_SYSTEM) {
-            for (String[] convertPref : CONVERT_PREFS) {
-                if (Prefs.getBoolean(mContext, convertPref[0], false)) {
-                    setTileAdded(convertPref[1]);
-                    Prefs.remove(mContext, convertPref[0]);
-                }
-            }
-        }
-        mContext.getContentResolver().registerContentObserver(
-                Secure.getUriFor(Secure.QS_AUTO_ADDED_TILES), false, mObserver,
-                UserHandle.USER_ALL);
-    }
-
-    @Override
-    public void changeUser(UserHandle newUser) {
-        if (newUser.getIdentifier() == mUserId) {
-            return;
-        }
-        mUserId = newUser.getIdentifier();
-        mAutoAdded.clear();
-        mAutoAdded.addAll(getAdded());
-    }
-
-    @Override
-    public int getCurrentUserId() {
-        return mUserId;
-    }
-
-    public boolean isAdded(String tile) {
-        return mAutoAdded.contains(tile);
-    }
-
-    public void setTileAdded(String tile) {
-        if (mAutoAdded.add(tile)) {
-            saveTiles();
-        }
-    }
-
-    public void setTileRemoved(String tile) {
-        if (mAutoAdded.remove(tile)) {
-            saveTiles();
-        }
-    }
-
-    public void destroy() {
-        mContext.getContentResolver().unregisterContentObserver(mObserver);
-    }
-
-    private void saveTiles() {
-        Secure.putStringForUser(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES,
-                TextUtils.join(",", mAutoAdded), mUserId);
-    }
-
-    private Collection<String> getAdded() {
-        String current = Secure.getStringForUser(mContext.getContentResolver(),
-                Secure.QS_AUTO_ADDED_TILES, mUserId);
-        if (current == null) {
-            return Collections.emptyList();
-        }
-        return Arrays.asList(current.split(","));
-    }
-
-    @VisibleForTesting
-    protected final ContentObserver mObserver = new ContentObserver(new Handler()) {
-        @Override
-        public void onChange(boolean selfChange) {
-            mAutoAdded.clear();
-            mAutoAdded.addAll(getAdded());
-        }
-    };
-
-    public static class Builder {
-        private final Context mContext;
-        private int mUserId;
-
-        @Inject
-        public Builder(Context context) {
-            mContext = context;
-        }
-
-        public Builder setUserId(int userId) {
-            mUserId = userId;
-            return this;
-        }
-
-        public AutoAddTracker build() {
-            return new AutoAddTracker(mContext, mUserId);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
new file mode 100644
index 0000000..7ffa9d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import android.text.TextUtils
+import android.util.ArraySet
+import android.util.Log
+import androidx.annotation.GuardedBy
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.UserAwareController
+import com.android.systemui.util.settings.SecureSettings
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private const val TAG = "AutoAddTracker"
+
+/**
+ * Class to track tiles that have been auto-added
+ *
+ * The list is backed by [Settings.Secure.QS_AUTO_ADDED_TILES].
+ *
+ * It also handles restore gracefully.
+ */
+class AutoAddTracker @VisibleForTesting constructor(
+    private val secureSettings: SecureSettings,
+    private val broadcastDispatcher: BroadcastDispatcher,
+    private val qsHost: QSHost,
+    private val dumpManager: DumpManager,
+    private val mainHandler: Handler?,
+    private val backgroundExecutor: Executor,
+    private var userId: Int
+) : UserAwareController, Dumpable {
+
+    companion object {
+        private val FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED)
+    }
+
+    @GuardedBy("autoAdded")
+    private val autoAdded = ArraySet<String>()
+    private var restoredTiles: Set<String>? = null
+
+    override val currentUserId: Int
+        get() = userId
+
+    private val contentObserver = object : ContentObserver(mainHandler) {
+        override fun onChange(
+            selfChange: Boolean,
+            uris: Collection<Uri>,
+            flags: Int,
+            _userId: Int
+        ) {
+            if (_userId != userId) {
+                // Ignore changes outside of our user. We'll load the correct value on user change
+                return
+            }
+            loadTiles()
+        }
+    }
+
+    private val restoreReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            if (intent.action != Intent.ACTION_SETTING_RESTORED) return
+            processRestoreIntent(intent)
+        }
+    }
+
+    private fun processRestoreIntent(intent: Intent) {
+        when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) {
+            Settings.Secure.QS_TILES -> {
+                restoredTiles = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
+                        ?.split(",")
+                        ?.toSet()
+                        ?: run {
+                            Log.w(TAG, "Null restored tiles for user $userId")
+                            emptySet()
+                        }
+            }
+            Settings.Secure.QS_AUTO_ADDED_TILES -> {
+                restoredTiles?.let { tiles ->
+                    val restoredAutoAdded = intent
+                            .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
+                            ?.split(",")
+                            ?: emptyList()
+                    val autoAddedBeforeRestore = intent
+                            .getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)
+                            ?.split(",")
+                            ?: emptyList()
+
+                    val tilesToRemove = restoredAutoAdded.filter { it !in tiles }
+                    if (tilesToRemove.isNotEmpty()) {
+                        qsHost.removeTiles(tilesToRemove)
+                    }
+                    val tiles = synchronized(autoAdded) {
+                        autoAdded.clear()
+                        autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore)
+                        getTilesFromListLocked()
+                    }
+                    saveTiles(tiles)
+                } ?: run {
+                    Log.w(TAG, "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " +
+                            "${Settings.Secure.QS_TILES} for user $userId")
+                }
+            }
+            else -> {} // Do nothing for other Settings
+        }
+    }
+
+    /**
+     * Init method must be called after construction to start listening
+     */
+    fun initialize() {
+        dumpManager.registerDumpable(TAG, this)
+        loadTiles()
+        secureSettings.registerContentObserverForUser(
+                secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES),
+                contentObserver,
+                UserHandle.USER_ALL
+        )
+        registerBroadcastReceiver()
+    }
+
+    /**
+     * Unregister listeners, receivers and observers
+     */
+    fun destroy() {
+        dumpManager.unregisterDumpable(TAG)
+        secureSettings.unregisterContentObserver(contentObserver)
+        unregisterBroadcastReceiver()
+    }
+
+    private fun registerBroadcastReceiver() {
+        broadcastDispatcher.registerReceiver(
+                restoreReceiver,
+                FILTER,
+                backgroundExecutor,
+                UserHandle.of(userId)
+        )
+    }
+
+    private fun unregisterBroadcastReceiver() {
+        broadcastDispatcher.unregisterReceiver(restoreReceiver)
+    }
+
+    override fun changeUser(newUser: UserHandle) {
+        if (newUser.identifier == userId) return
+        unregisterBroadcastReceiver()
+        userId = newUser.identifier
+        restoredTiles = null
+        loadTiles()
+        registerBroadcastReceiver()
+    }
+
+    /**
+     * Returns `true` if the tile has been auto-added before
+     */
+    fun isAdded(tile: String): Boolean {
+        return synchronized(autoAdded) {
+            tile in autoAdded
+        }
+    }
+
+    /**
+     * Sets a tile as auto-added.
+     *
+     * From here on, [isAdded] will return true for that tile.
+     */
+    fun setTileAdded(tile: String) {
+        val tiles = synchronized(autoAdded) {
+                if (autoAdded.add(tile)) {
+                    getTilesFromListLocked()
+                } else {
+                    null
+                }
+            }
+        tiles?.let { saveTiles(it) }
+    }
+
+    /**
+     * Removes a tile from the list of auto-added.
+     *
+     * This allows for this tile to be auto-added again in the future.
+     */
+    fun setTileRemoved(tile: String) {
+        val tiles = synchronized(autoAdded) {
+            if (autoAdded.remove(tile)) {
+                getTilesFromListLocked()
+            } else {
+                null
+            }
+        }
+        tiles?.let { saveTiles(it) }
+    }
+
+    private fun getTilesFromListLocked(): String {
+        return TextUtils.join(",", autoAdded)
+    }
+
+    private fun saveTiles(tiles: String) {
+        secureSettings.putStringForUser(
+                Settings.Secure.QS_AUTO_ADDED_TILES,
+                tiles,
+                /* tag */ null,
+                /* makeDefault */ false,
+                userId,
+                /* overrideableByRestore */ true
+        )
+    }
+
+    private fun loadTiles() {
+        synchronized(autoAdded) {
+            autoAdded.clear()
+            autoAdded.addAll(getAdded())
+        }
+    }
+
+    private fun getAdded(): Collection<String> {
+        val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId)
+        return current?.split(",") ?: emptySet()
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        pw.println("Current user: $userId")
+        pw.println("Added tiles: $autoAdded")
+    }
+
+    @SysUISingleton
+    class Builder @Inject constructor(
+        private val secureSettings: SecureSettings,
+        private val broadcastDispatcher: BroadcastDispatcher,
+        private val qsHost: QSHost,
+        private val dumpManager: DumpManager,
+        @Main private val handler: Handler,
+        @Background private val executor: Executor
+    ) {
+        private var userId: Int = 0
+
+        fun setUserId(_userId: Int): Builder {
+            userId = _userId
+            return this
+        }
+
+        fun build(): AutoAddTracker {
+            return AutoAddTracker(
+                    secureSettings,
+                    broadcastDispatcher,
+                    qsHost,
+                    dumpManager,
+                    handler,
+                    executor,
+                    userId
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 000fd1c..9f585bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -37,6 +37,7 @@
     void removeCallback(Callback callback);
     TileServices getTileServices();
     void removeTile(String tileSpec);
+    void removeTiles(Collection<String> specs);
     void unmarkTileAsAutoAdded(String tileSpec);
 
     int indexOf(String tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 4a75810..e60fb49 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -351,6 +351,17 @@
         changeTileSpecs(tileSpecs-> tileSpecs.remove(spec));
     }
 
+    /**
+     * Remove many tiles at once.
+     *
+     * It will only save to settings once (as opposed to {@link QSTileHost#removeTile} called
+     * multiple times).
+     */
+    @Override
+    public void removeTiles(Collection<String> specs) {
+        changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs));
+    }
+
     @Override
     public void unmarkTileAsAutoAdded(String spec) {
         if (mAutoTiles != null) mAutoTiles.unmarkTileAsAutoAdded(spec);
@@ -372,6 +383,7 @@
      * @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X
      */
     public void addTile(String spec, int requestPosition) {
+        if (spec.equals("work")) Log.wtfStack(TAG, "Adding work tile");
         changeTileSpecs(tileSpecs -> {
             if (tileSpecs.contains(spec)) return false;
 
@@ -386,6 +398,7 @@
     }
 
     void saveTilesToSettings(List<String> tileSpecs) {
+        if (tileSpecs.contains("work")) Log.wtfStack(TAG, "Saving work tile");
         mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
                 null /* tag */, false /* default */, mCurrentUser,
                 true /* overrideable by restore */);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 77906ab..5838b8f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -42,6 +42,7 @@
 import com.android.systemui.statusbar.phone.StatusBarWindowView;
 import com.android.systemui.statusbar.phone.StatusIconContainer;
 import com.android.systemui.statusbar.policy.Clock;
+import com.android.systemui.statusbar.policy.VariableDateView;
 
 import java.util.List;
 
@@ -62,11 +63,14 @@
     protected QuickQSPanel mHeaderQsPanel;
     private View mDatePrivacyView;
     private View mDateView;
+    // DateView next to clock. Visible on QQS
+    private VariableDateView mClockDateView;
     private View mSecurityHeaderView;
     private View mClockIconsView;
     private View mContainer;
 
     private View mQSCarriers;
+    private ViewGroup mClockContainer;
     private Clock mClockView;
     private Space mDatePrivacySeparator;
     private View mClockIconsSeparator;
@@ -86,7 +90,6 @@
     private int mWaterfallTopInset;
     private int mCutOutPaddingLeft;
     private int mCutOutPaddingRight;
-    private float mViewAlpha = 1.0f;
     private float mKeyguardExpansionFraction;
     private int mTextColorPrimary = Color.TRANSPARENT;
     private int mTopViewMeasureHeight;
@@ -123,18 +126,24 @@
         mIconContainer = findViewById(R.id.statusIcons);
         mPrivacyChip = findViewById(R.id.privacy_chip);
         mDateView = findViewById(R.id.date);
+        mClockDateView = findViewById(R.id.date_clock);
         mSecurityHeaderView = findViewById(R.id.header_text_container);
         mClockIconsSeparator = findViewById(R.id.separator);
         mRightLayout = findViewById(R.id.rightLayout);
         mDateContainer = findViewById(R.id.date_container);
         mPrivacyContainer = findViewById(R.id.privacy_container);
 
+        mClockContainer = findViewById(R.id.clock_container);
         mClockView = findViewById(R.id.clock);
         mDatePrivacySeparator = findViewById(R.id.space);
         // Tint for the battery icons are handled in setupHost()
         mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon);
 
         updateResources();
+        Configuration config = mContext.getResources().getConfiguration();
+        setDatePrivacyContainersWidth(config.orientation == Configuration.ORIENTATION_LANDSCAPE);
+        setSecurityHeaderContainerVisibility(
+                config.orientation == Configuration.ORIENTATION_LANDSCAPE);
 
         // Don't need to worry about tuner settings for this icon
         mBatteryRemainingIcon.setIgnoreTunerUpdates(true);
@@ -177,7 +186,7 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         if (mDatePrivacyView.getMeasuredHeight() != mTopViewMeasureHeight) {
             mTopViewMeasureHeight = mDatePrivacyView.getMeasuredHeight();
-            updateAnimators();
+            post(this::updateAnimators);
         }
     }
 
@@ -186,6 +195,8 @@
         super.onConfigurationChanged(newConfig);
         updateResources();
         setDatePrivacyContainersWidth(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
+        setSecurityHeaderContainerVisibility(
+                newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
     }
 
     @Override
@@ -206,6 +217,10 @@
         mPrivacyContainer.setLayoutParams(lp);
     }
 
+    private void setSecurityHeaderContainerVisibility(boolean landscape) {
+        mSecurityHeaderView.setVisibility(landscape ? VISIBLE : GONE);
+    }
+
     private void updateBatteryMode() {
         if (mConfigShowBatteryEstimate && !mHasCenterCutout) {
             mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
@@ -280,7 +295,8 @@
         TouchAnimator.Builder builder = new TouchAnimator.Builder()
                 .addFloat(mSecurityHeaderView, "alpha", 0, 1)
                 // These views appear on expanding down
-                .addFloat(mClockView, "alpha", 0, 1)
+                .addFloat(mDateView, "alpha", 0, 0, 1)
+                .addFloat(mClockDateView, "alpha", 1, 0, 0)
                 .addFloat(mQSCarriers, "alpha", 0, 1)
                 .setListener(new TouchAnimator.ListenerAdapter() {
                     @Override
@@ -289,10 +305,14 @@
                         if (!mIsSingleCarrier) {
                             mIconContainer.addIgnoredSlots(mRssiIgnoredSlots);
                         }
+                        // Make it gone so there's enough room for carrier names
+                        mClockDateView.setVisibility(View.GONE);
                     }
 
                     @Override
                     public void onAnimationStarted() {
+                        mClockDateView.setVisibility(View.VISIBLE);
+                        mClockDateView.setFreezeSwitching(true);
                         setSeparatorVisibility(false);
                         if (!mIsSingleCarrier) {
                             mIconContainer.addIgnoredSlots(mRssiIgnoredSlots);
@@ -302,6 +322,8 @@
                     @Override
                     public void onAnimationAtStart() {
                         super.onAnimationAtStart();
+                        mClockDateView.setFreezeSwitching(false);
+                        mClockDateView.setVisibility(View.VISIBLE);
                         setSeparatorVisibility(mShowClockIconsSeparator);
                         // In QQS we never ignore RSSI.
                         mIconContainer.removeIgnoredSlots(mRssiIgnoredSlots);
@@ -434,10 +456,11 @@
         mClockIconsSeparator.setVisibility(visible ? View.VISIBLE : View.GONE);
         mQSCarriers.setVisibility(visible ? View.GONE : View.VISIBLE);
 
-        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mClockView.getLayoutParams();
+        LinearLayout.LayoutParams lp =
+                (LinearLayout.LayoutParams) mClockContainer.getLayoutParams();
         lp.width = visible ? 0 : WRAP_CONTENT;
         lp.weight = visible ? 1f : 0f;
-        mClockView.setLayoutParams(lp);
+        mClockContainer.setLayoutParams(lp);
 
         lp = (LinearLayout.LayoutParams) mRightLayout.getLayoutParams();
         lp.width = visible ? 0 : WRAP_CONTENT;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index da75c9e..18d6e64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -41,6 +41,7 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusIconContainer;
 import com.android.systemui.statusbar.policy.Clock;
+import com.android.systemui.statusbar.policy.VariableDateViewController;
 import com.android.systemui.util.ViewController;
 
 import java.util.List;
@@ -71,6 +72,9 @@
     private final QSExpansionPathInterpolator mQSExpansionPathInterpolator;
     private final FeatureFlags mFeatureFlags;
 
+    private final VariableDateViewController mVariableDateViewControllerDateView;
+    private final VariableDateViewController mVariableDateViewControllerClockDateView;
+
     private boolean mListening;
     private boolean mMicCameraIndicatorsEnabled;
     private boolean mLocationIndicatorsEnabled;
@@ -134,7 +138,8 @@
             SysuiColorExtractor colorExtractor,
             PrivacyDialogController privacyDialogController,
             QSExpansionPathInterpolator qsExpansionPathInterpolator,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            VariableDateViewController.Factory variableDateViewControllerFactory) {
         super(view);
         mPrivacyItemController = privacyItemController;
         mActivityStarter = activityStarter;
@@ -154,6 +159,12 @@
         mPrivacyChip = mView.findViewById(R.id.privacy_chip);
         mClockView = mView.findViewById(R.id.clock);
         mIconContainer = mView.findViewById(R.id.statusIcons);
+        mVariableDateViewControllerDateView = variableDateViewControllerFactory.create(
+                mView.requireViewById(R.id.date)
+        );
+        mVariableDateViewControllerClockDateView = variableDateViewControllerFactory.create(
+                mView.requireViewById(R.id.date_clock)
+        );
 
         mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, featureFlags);
         mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
@@ -205,6 +216,9 @@
         mView.onAttach(mIconManager, mQSExpansionPathInterpolator, rssiIgnoredSlots);
 
         mDemoModeController.addCallback(mDemoModeReceiver);
+
+        mVariableDateViewControllerDateView.init();
+        mVariableDateViewControllerClockDateView.init();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 2e771d6..b1cd03c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -116,6 +116,9 @@
                     : icon.getInvisibleDrawable(mContext) : null;
             int padding = icon != null ? icon.getPadding() : 0;
             if (d != null) {
+                if (d.getConstantState() != null) {
+                    d = d.getConstantState().newDrawable();
+                }
                 d.setAutoMirrored(false);
                 d.setLayoutDirection(getLayoutDirection());
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 4b13015..04f089d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -150,11 +150,7 @@
         }
 
         List<CastDevice> activeDevices = getActiveDevices();
-        // We want to pop up the media route selection dialog if we either have no active devices
-        // (neither routes nor projection), or if we have an active route. In other cases, we assume
-        // that a projection is active. This is messy, but this tile never correctly handled the
-        // case where multiple devices were active :-/.
-        if (activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo)) {
+        if (willPopDetail()) {
             mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
                 showDetail(true);
             });
@@ -163,6 +159,15 @@
         }
     }
 
+    // We want to pop up the media route selection dialog if we either have no active devices
+    // (neither routes nor projection), or if we have an active route. In other cases, we assume
+    // that a projection is active. This is messy, but this tile never correctly handled the
+    // case where multiple devices were active :-/.
+    private boolean willPopDetail() {
+        List<CastDevice> activeDevices = getActiveDevices();
+        return activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo);
+    }
+
     private List<CastDevice> getActiveDevices() {
         ArrayList<CastDevice> activeDevices = new ArrayList<>();
         for (CastDevice device : mController.getCastDevices()) {
@@ -234,10 +239,12 @@
             state.contentDescription = state.contentDescription + ","
                     + mContext.getString(R.string.accessibility_quick_settings_open_details);
             state.expandedAccessibilityClassName = Button.class.getName();
+            state.forceExpandIcon = willPopDetail();
         } else {
             state.state = Tile.STATE_UNAVAILABLE;
             String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
             state.secondaryLabel = noWifi;
+            state.forceExpandIcon = false;
         }
         state.stateDescription = state.stateDescription + ", " + state.secondaryLabel;
         mDetailAdapter.updateItems(devices);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 7cb1421..cc9e748 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -51,7 +51,9 @@
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
@@ -66,15 +68,16 @@
 /** Quick settings tile: Internet **/
 public class InternetTile extends QSTileImpl<SignalState> {
     private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
-    private static final Intent INTERNET_PANEL =
-            new Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY);
 
     protected final NetworkController mController;
+    private final AccessPointController mAccessPointController;
     private final DataUsageController mDataController;
     // The last updated tile state, 0: mobile, 1: wifi, 2: ethernet.
     private int mLastTileState = -1;
 
     protected final InternetSignalCallback mSignalCallback = new InternetSignalCallback();
+    private final InternetDialogFactory mInternetDialogFactory;
+    final Handler mHandler;
 
     @Inject
     public InternetTile(
@@ -86,11 +89,16 @@
             StatusBarStateController statusBarStateController,
             ActivityStarter activityStarter,
             QSLogger qsLogger,
-            NetworkController networkController
+            NetworkController networkController,
+            AccessPointController accessPointController,
+            InternetDialogFactory internetDialogFactory
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
+        mInternetDialogFactory = internetDialogFactory;
+        mHandler = mainHandler;
         mController = networkController;
+        mAccessPointController = accessPointController;
         mDataController = mController.getMobileDataController();
         mController.observe(getLifecycle(), mSignalCallback);
     }
@@ -114,7 +122,9 @@
 
     @Override
     protected void handleClick(@Nullable View view) {
-        mActivityStarter.postStartActivityDismissingKeyguard(INTERNET_PANEL, 0);
+        mHandler.post(() -> mInternetDialogFactory.create(true,
+                mAccessPointController.canConfigMobileData(),
+                mAccessPointController.canConfigWifi()));
     }
 
     @Override
@@ -429,7 +439,7 @@
                 state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
             }
         } else if (cb.mNoDefaultNetwork) {
-            if (cb.mNoNetworksAvailable) {
+            if (cb.mNoNetworksAvailable || !cb.mEnabled) {
                 state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
                 state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
             } else {
@@ -489,7 +499,7 @@
             state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
             state.secondaryLabel = r.getString(R.string.status_bar_airplane);
         } else if (cb.mNoDefaultNetwork) {
-            if (cb.mNoNetworksAvailable) {
+            if (cb.mNoNetworksAvailable || !mSignalCallback.mWifiInfo.mEnabled) {
                 state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
                 state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
new file mode 100644
index 0000000..99eb5b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.text.Html;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.Utils;
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.wifitrackerlib.WifiEntry;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Adapter for showing Wi-Fi networks.
+ */
+public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.InternetViewHolder> {
+
+    private static final String TAG = "InternetAdapter";
+    private static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG";
+    private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
+    private static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller";
+
+    private final InternetDialogController mInternetDialogController;
+    private List<WifiEntry> mWifiEntries;
+    private int mWifiEntriesCount;
+
+    protected View mHolderView;
+    protected Context mContext;
+
+    public InternetAdapter(InternetDialogController controller) {
+        mInternetDialogController = controller;
+    }
+
+    @Override
+    public InternetViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
+            int viewType) {
+        mContext = viewGroup.getContext();
+        mHolderView = LayoutInflater.from(mContext).inflate(R.layout.internet_list_item,
+                viewGroup, false);
+        return new InternetViewHolder(mHolderView, mInternetDialogController);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull InternetViewHolder viewHolder, int position) {
+        if (mWifiEntries == null || position >= mWifiEntriesCount) {
+            return;
+        }
+        viewHolder.onBind(mWifiEntries.get(position));
+    }
+
+    /**
+     * Updates the Wi-Fi networks.
+     *
+     * @param wifiEntries the updated Wi-Fi entries.
+     * @param wifiEntriesCount the total number of Wi-Fi entries.
+     */
+    public void setWifiEntries(@Nullable List<WifiEntry> wifiEntries, int wifiEntriesCount) {
+        mWifiEntries = wifiEntries;
+        mWifiEntriesCount = wifiEntriesCount;
+    }
+
+    /**
+     * Gets the total number of Wi-Fi networks.
+     *
+     * @return The total number of Wi-Fi entries.
+     */
+    @Override
+    public int getItemCount() {
+        return mWifiEntriesCount;
+    }
+
+    /**
+     * ViewHolder for binding Wi-Fi view.
+     */
+    static class InternetViewHolder extends RecyclerView.ViewHolder {
+
+        final LinearLayout mContainerLayout;
+        final LinearLayout mWifiListLayout;
+        final LinearLayout mWifiNetworkLayout;
+        final ImageView mWifiIcon;
+        final TextView mWifiTitleText;
+        final TextView mWifiSummaryText;
+        final ImageView mWifiEndIcon;
+        final Context mContext;
+        final InternetDialogController mInternetDialogController;
+
+        @VisibleForTesting
+        protected WifiUtils.InternetIconInjector mWifiIconInjector;
+
+        InternetViewHolder(View view, InternetDialogController internetDialogController) {
+            super(view);
+            mContext = view.getContext();
+            mInternetDialogController = internetDialogController;
+            mContainerLayout = view.requireViewById(R.id.internet_container);
+            mWifiListLayout = view.requireViewById(R.id.wifi_list);
+            mWifiNetworkLayout = view.requireViewById(R.id.wifi_network_layout);
+            mWifiIcon = view.requireViewById(R.id.wifi_icon);
+            mWifiTitleText = view.requireViewById(R.id.wifi_title);
+            mWifiSummaryText = view.requireViewById(R.id.wifi_summary);
+            mWifiEndIcon = view.requireViewById(R.id.wifi_end_icon);
+            mWifiIconInjector = mInternetDialogController.getWifiIconInjector();
+        }
+
+        void onBind(@NonNull WifiEntry wifiEntry) {
+            mWifiIcon.setImageDrawable(getWifiDrawable(wifiEntry));
+            setWifiNetworkLayout(wifiEntry.getTitle(),
+                    Html.fromHtml(wifiEntry.getSummary(false), Html.FROM_HTML_MODE_LEGACY));
+
+            final int connectedState = wifiEntry.getConnectedState();
+            final int security = wifiEntry.getSecurity();
+            updateEndIcon(connectedState, security);
+
+            if (connectedState != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
+                mWifiListLayout.setOnClickListener(
+                        v -> mInternetDialogController.launchWifiNetworkDetailsSetting(
+                                wifiEntry.getKey()));
+                return;
+            }
+            mWifiListLayout.setOnClickListener(v -> {
+                if (wifiEntry.shouldEditBeforeConnect()) {
+                    final Intent intent = new Intent(ACTION_WIFI_DIALOG);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+                    intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, wifiEntry.getKey());
+                    intent.putExtra(EXTRA_CONNECT_FOR_CALLER, false);
+                    mContext.startActivity(intent);
+                }
+                mInternetDialogController.connect(wifiEntry);
+            });
+        }
+
+        void setWifiNetworkLayout(CharSequence title, CharSequence summary) {
+            mWifiTitleText.setText(title);
+            if (TextUtils.isEmpty(summary)) {
+                mWifiSummaryText.setVisibility(View.GONE);
+                return;
+            }
+            mWifiSummaryText.setVisibility(View.VISIBLE);
+            mWifiSummaryText.setText(summary);
+        }
+
+        Drawable getWifiDrawable(@NonNull WifiEntry wifiEntry) {
+            if (wifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
+                return null;
+            }
+            final Drawable drawable = mWifiIconInjector.getIcon(wifiEntry.shouldShowXLevelIcon(),
+                    wifiEntry.getLevel());
+            if (drawable == null) {
+                return null;
+            }
+            drawable.setTint(
+                    Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorTertiary));
+            final AtomicReference<Drawable> shared = new AtomicReference<>();
+            shared.set(drawable);
+            return shared.get();
+        }
+
+        void updateEndIcon(int connectedState, int security) {
+            Drawable drawable = null;
+            if (connectedState != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
+                drawable = mContext.getDrawable(R.drawable.ic_settings_24dp);
+            } else if (security != WifiEntry.SECURITY_NONE && security != WifiEntry.SECURITY_OWE) {
+                drawable = mContext.getDrawable(R.drawable.ic_friction_lock_closed);
+            }
+            if (drawable == null) {
+                mWifiEndIcon.setVisibility(View.GONE);
+                return;
+            }
+            mWifiEndIcon.setVisibility(View.VISIBLE);
+            mWifiEndIcon.setImageDrawable(drawable);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
new file mode 100644
index 0000000..b1db8a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.tiles.dialog;
+
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+
+import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
+import android.text.Html;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.settingslib.Utils;
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.wifitrackerlib.WifiEntry;
+
+import java.util.List;
+
+/**
+ * Dialog for showing mobile network, connected Wi-Fi network and Wi-Fi networks.
+ */
+@SysUISingleton
+public class InternetDialog extends SystemUIDialog implements
+        InternetDialogController.InternetDialogCallback, Window.Callback {
+    private static final String TAG = "InternetDialog";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    static final long PROGRESS_DELAY_MS = 2000L;
+
+    private final Handler mHandler;
+    private final LinearLayoutManager mLayoutManager;
+
+    @VisibleForTesting
+    protected InternetAdapter mAdapter;
+    @VisibleForTesting
+    protected WifiManager mWifiManager;
+    @VisibleForTesting
+    protected View mDialogView;
+    @VisibleForTesting
+    protected boolean mCanConfigWifi;
+
+    private InternetDialogFactory mInternetDialogFactory;
+    private SubscriptionManager mSubscriptionManager;
+    private TelephonyManager mTelephonyManager;
+    private AlertDialog mAlertDialog;
+    private UiEventLogger mUiEventLogger;
+    private Context mContext;
+    private InternetDialogController mInternetDialogController;
+    private TextView mInternetDialogTitle;
+    private TextView mInternetDialogSubTitle;
+    private View mDivider;
+    private ProgressBar mProgressBar;
+    private LinearLayout mInternetDialogLayout;
+    private LinearLayout mInternetListLayout;
+    private LinearLayout mConnectedWifListLayout;
+    private LinearLayout mConnectedWifList;
+    private LinearLayout mMobileNetworkLayout;
+    private LinearLayout mMobileNetworkList;
+    private LinearLayout mTurnWifiOnLayout;
+    private TextView mWifiToggleTitleText;
+    private LinearLayout mSeeAllLayout;
+    private RecyclerView mWifiRecyclerView;
+    private ImageView mConnectedWifiIcon;
+    private ImageView mWifiSettingsIcon;
+    private TextView mConnectedWifiTitleText;
+    private TextView mConnectedWifiSummaryText;
+    private ImageView mSignalIcon;
+    private TextView mMobileTitleText;
+    private TextView mMobileSummaryText;
+    private Switch mMobileDataToggle;
+    private Switch mWiFiToggle;
+    private Button mDoneButton;
+    private Drawable mBackgroundOn;
+    private int mListMaxHeight;
+    private int mLayoutWidth;
+    private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private boolean mCanConfigMobileData;
+
+    // Wi-Fi entries
+    protected WifiEntry mConnectedWifiEntry;
+    protected int mWifiEntriesCount;
+
+    // Wi-Fi scanning progress bar
+    protected boolean mIsProgressBarVisible;
+    protected boolean mIsSearchingHidden;
+    protected final Runnable mHideProgressBarRunnable = () -> {
+        setProgressBarVisible(false);
+    };
+    protected Runnable mHideSearchingRunnable = () -> {
+        mIsSearchingHidden = true;
+        mInternetDialogSubTitle.setText(getSubtitleText());
+    };
+
+    private final ViewTreeObserver.OnGlobalLayoutListener mInternetListLayoutListener = () -> {
+        // Set max height for list
+        if (mInternetDialogLayout.getHeight() > mListMaxHeight) {
+            ViewGroup.LayoutParams params = mInternetDialogLayout.getLayoutParams();
+            params.height = mListMaxHeight;
+            mInternetDialogLayout.setLayoutParams(params);
+        }
+    };
+
+    public InternetDialog(Context context, InternetDialogFactory internetDialogFactory,
+            InternetDialogController internetDialogController, boolean canConfigMobileData,
+            boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger,
+            @Main Handler handler) {
+        super(context, R.style.Theme_SystemUI_Dialog_Internet);
+        if (DEBUG) {
+            Log.d(TAG, "Init InternetDialog");
+        }
+        mContext = context;
+        mHandler = handler;
+        mInternetDialogFactory = internetDialogFactory;
+        mInternetDialogController = internetDialogController;
+        mSubscriptionManager = mInternetDialogController.getSubscriptionManager();
+        mDefaultDataSubId = mInternetDialogController.getDefaultDataSubscriptionId();
+        mTelephonyManager = mInternetDialogController.getTelephonyManager();
+        mWifiManager = mInternetDialogController.getWifiManager();
+        mCanConfigMobileData = canConfigMobileData;
+        mCanConfigWifi = canConfigWifi;
+
+        mLayoutManager = new LinearLayoutManager(mContext) {
+            @Override
+            public boolean canScrollVertically() {
+                return false;
+            }
+        };
+        mListMaxHeight = context.getResources().getDimensionPixelSize(
+                R.dimen.internet_dialog_list_max_height);
+        mLayoutWidth = context.getResources().getDimensionPixelSize(
+                R.dimen.internet_dialog_list_max_width);
+        mUiEventLogger = uiEventLogger;
+        mAdapter = new InternetAdapter(mInternetDialogController);
+        if (!aboveStatusBar) {
+            getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (DEBUG) {
+            Log.d(TAG, "onCreate");
+        }
+        mUiEventLogger.log(InternetDialogEvent.INTERNET_DIALOG_SHOW);
+        mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog,
+                null);
+        final Window window = getWindow();
+        final WindowManager.LayoutParams layoutParams = window.getAttributes();
+        layoutParams.gravity = Gravity.BOTTOM;
+        // Move down the dialog to overlay the navigation bar.
+        layoutParams.setFitInsetsTypes(
+                layoutParams.getFitInsetsTypes() & ~WindowInsets.Type.navigationBars());
+        layoutParams.setFitInsetsSides(WindowInsets.Side.all());
+        layoutParams.setFitInsetsIgnoringVisibility(true);
+        window.setAttributes(layoutParams);
+        window.setContentView(mDialogView);
+        //Only fix the width for large screen or tablet.
+        window.setLayout(mContext.getResources().getDimensionPixelSize(
+                R.dimen.internet_dialog_list_max_width), ViewGroup.LayoutParams.WRAP_CONTENT);
+        window.setWindowAnimations(R.style.Animation_InternetDialog);
+        window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+        window.addFlags(FLAG_LAYOUT_NO_LIMITS);
+
+        mInternetDialogLayout = mDialogView.requireViewById(R.id.internet_connectivity_dialog);
+        mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title);
+        mInternetDialogSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
+        mDivider = mDialogView.requireViewById(R.id.divider);
+        mProgressBar = mDialogView.requireViewById(R.id.wifi_searching_progress);
+        mInternetListLayout = mDialogView.requireViewById(R.id.internet_list);
+        mMobileNetworkLayout = mDialogView.requireViewById(R.id.mobile_network_layout);
+        mMobileNetworkList = mDialogView.requireViewById(R.id.mobile_network_list);
+        mTurnWifiOnLayout = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
+        mWifiToggleTitleText = mDialogView.requireViewById(R.id.wifi_toggle_title);
+        mConnectedWifListLayout = mDialogView.requireViewById(R.id.wifi_connected_layout);
+        mConnectedWifList = mDialogView.requireViewById(R.id.wifi_connected_list);
+        mConnectedWifiIcon = mDialogView.requireViewById(R.id.wifi_connected_icon);
+        mConnectedWifiTitleText = mDialogView.requireViewById(R.id.wifi_connected_title);
+        mConnectedWifiSummaryText = mDialogView.requireViewById(R.id.wifi_connected_summary);
+        mWifiSettingsIcon = mDialogView.requireViewById(R.id.wifi_settings_icon);
+        mWifiRecyclerView = mDialogView.requireViewById(R.id.wifi_list_layout);
+        mSeeAllLayout = mDialogView.requireViewById(R.id.see_all_layout);
+        mDoneButton = mDialogView.requireViewById(R.id.done);
+        mSignalIcon = mDialogView.requireViewById(R.id.signal_icon);
+        mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title);
+        mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary);
+        mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle);
+        mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle);
+        mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
+        mInternetDialogLayout.getViewTreeObserver().addOnGlobalLayoutListener(
+                mInternetListLayoutListener);
+        mInternetDialogTitle.setText(getDialogTitleText());
+        mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
+
+        setOnClickListener();
+        mTurnWifiOnLayout.setBackground(null);
+        mWifiRecyclerView.setLayoutManager(mLayoutManager);
+        mWifiRecyclerView.setAdapter(mAdapter);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        if (DEBUG) {
+            Log.d(TAG, "onStart");
+        }
+        mInternetDialogController.onStart(this, mCanConfigWifi);
+        if (!mCanConfigWifi) {
+            hideWifiViews();
+        }
+    }
+
+    @VisibleForTesting
+    void hideWifiViews() {
+        setProgressBarVisible(false);
+        mTurnWifiOnLayout.setVisibility(View.GONE);
+        mConnectedWifListLayout.setVisibility(View.GONE);
+        mWifiRecyclerView.setVisibility(View.GONE);
+        mSeeAllLayout.setVisibility(View.GONE);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        if (DEBUG) {
+            Log.d(TAG, "onStop");
+        }
+        mHandler.removeCallbacks(mHideProgressBarRunnable);
+        mHandler.removeCallbacks(mHideSearchingRunnable);
+        mMobileNetworkLayout.setOnClickListener(null);
+        mMobileDataToggle.setOnCheckedChangeListener(null);
+        mConnectedWifListLayout.setOnClickListener(null);
+        mSeeAllLayout.setOnClickListener(null);
+        mWiFiToggle.setOnCheckedChangeListener(null);
+        mDoneButton.setOnClickListener(null);
+        mInternetDialogController.onStop();
+        mInternetDialogFactory.destroyDialog();
+    }
+
+    @Override
+    public void dismissDialog() {
+        if (DEBUG) {
+            Log.d(TAG, "dismissDialog");
+        }
+        mInternetDialogFactory.destroyDialog();
+        dismiss();
+    }
+
+    void updateDialog() {
+        if (DEBUG) {
+            Log.d(TAG, "updateDialog");
+        }
+        if (mInternetDialogController.isAirplaneModeEnabled()) {
+            mInternetDialogSubTitle.setVisibility(View.GONE);
+        } else {
+            mInternetDialogSubTitle.setText(getSubtitleText());
+        }
+        setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular());
+
+        if (!mCanConfigWifi) {
+            return;
+        }
+
+        showProgressBar();
+        final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked();
+        final boolean isWifiEnabled = mWifiManager.isWifiEnabled();
+        updateWifiToggle(isWifiEnabled, isDeviceLocked);
+        updateConnectedWifi(isWifiEnabled, isDeviceLocked);
+
+        final int visibility = (isDeviceLocked || !isWifiEnabled || mWifiEntriesCount <= 0)
+                ? View.GONE : View.VISIBLE;
+        mWifiRecyclerView.setVisibility(visibility);
+        mSeeAllLayout.setVisibility(visibility);
+    }
+
+    private void setOnClickListener() {
+        mMobileNetworkLayout.setOnClickListener(v -> {
+            if (mInternetDialogController.isMobileDataEnabled()
+                    && !mInternetDialogController.isDeviceLocked()) {
+                if (!mInternetDialogController.activeNetworkIsCellular()) {
+                    mInternetDialogController.connectCarrierNetwork();
+                }
+            }
+        });
+        mMobileDataToggle.setOnCheckedChangeListener(
+                (buttonView, isChecked) -> {
+                    if (!isChecked && shouldShowMobileDialog()) {
+                        showTurnOffMobileDialog();
+                    } else if (!shouldShowMobileDialog()) {
+                        mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
+                                isChecked, false);
+                    }
+                });
+        mConnectedWifListLayout.setOnClickListener(v -> onClickConnectedWifi());
+        mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton());
+        mWiFiToggle.setOnCheckedChangeListener(
+                (buttonView, isChecked) -> {
+                    buttonView.setChecked(isChecked);
+                    mWifiManager.setWifiEnabled(isChecked);
+                });
+        mDoneButton.setOnClickListener(v -> dismiss());
+    }
+
+    private void setMobileDataLayout(boolean isCellularNetwork) {
+        if (mInternetDialogController.isAirplaneModeEnabled()
+                || !mInternetDialogController.hasCarrier()) {
+            mMobileNetworkLayout.setVisibility(View.GONE);
+        } else {
+            mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled());
+            mMobileNetworkLayout.setVisibility(View.VISIBLE);
+            mMobileTitleText.setText(getMobileNetworkTitle());
+            if (!TextUtils.isEmpty(getMobileNetworkSummary())) {
+                mMobileSummaryText.setText(
+                        Html.fromHtml(getMobileNetworkSummary(), Html.FROM_HTML_MODE_LEGACY));
+                mMobileSummaryText.setVisibility(View.VISIBLE);
+            } else {
+                mMobileSummaryText.setVisibility(View.GONE);
+            }
+            mSignalIcon.setImageDrawable(getSignalStrengthDrawable());
+            if (mInternetDialogController.isNightMode()) {
+                int titleColor = isCellularNetwork ? mContext.getColor(
+                        R.color.connected_network_primary_color) : Utils.getColorAttrDefaultColor(
+                        mContext, android.R.attr.textColorPrimary);
+                int summaryColor = isCellularNetwork ? mContext.getColor(
+                        R.color.connected_network_secondary_color) : Utils.getColorAttrDefaultColor(
+                        mContext, android.R.attr.textColorSecondary);
+
+                mMobileTitleText.setTextColor(titleColor);
+                mMobileSummaryText.setTextColor(summaryColor);
+            }
+            mMobileNetworkLayout.setBackground(isCellularNetwork ? mBackgroundOn : null);
+
+            mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
+        }
+    }
+
+    private void updateWifiToggle(boolean isWifiEnabled, boolean isDeviceLocked) {
+        mWiFiToggle.setChecked(isWifiEnabled);
+        if (isDeviceLocked && mInternetDialogController.isNightMode()) {
+            int titleColor = mConnectedWifiEntry != null ? mContext.getColor(
+                    R.color.connected_network_primary_color) : Utils.getColorAttrDefaultColor(
+                    mContext, android.R.attr.textColorPrimary);
+            mWifiToggleTitleText.setTextColor(titleColor);
+        }
+        mTurnWifiOnLayout.setBackground(
+                (isDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn : null);
+    }
+
+    private void updateConnectedWifi(boolean isWifiEnabled, boolean isDeviceLocked) {
+        if (!isWifiEnabled || mConnectedWifiEntry == null || isDeviceLocked) {
+            mConnectedWifListLayout.setBackground(null);
+            mConnectedWifListLayout.setVisibility(View.GONE);
+            return;
+        }
+        mConnectedWifListLayout.setVisibility(View.VISIBLE);
+        mConnectedWifiTitleText.setText(mConnectedWifiEntry.getTitle());
+        mConnectedWifiSummaryText.setText(mConnectedWifiEntry.getSummary(false));
+        mConnectedWifiIcon.setImageDrawable(
+                mInternetDialogController.getInternetWifiDrawable(mConnectedWifiEntry));
+        if (mInternetDialogController.isNightMode()) {
+            mConnectedWifiTitleText.setTextColor(
+                    mContext.getColor(R.color.connected_network_primary_color));
+            mConnectedWifiSummaryText.setTextColor(
+                    mContext.getColor(R.color.connected_network_secondary_color));
+        }
+        mWifiSettingsIcon.setColorFilter(
+                mContext.getColor(R.color.connected_network_primary_color));
+        mConnectedWifListLayout.setBackground(mBackgroundOn);
+    }
+
+    void onClickConnectedWifi() {
+        if (mConnectedWifiEntry == null) {
+            return;
+        }
+        mInternetDialogController.launchWifiNetworkDetailsSetting(mConnectedWifiEntry.getKey());
+    }
+
+    void onClickSeeMoreButton() {
+        mInternetDialogController.launchNetworkSetting();
+    }
+
+    CharSequence getDialogTitleText() {
+        return mInternetDialogController.getDialogTitleText();
+    }
+
+    CharSequence getSubtitleText() {
+        return mInternetDialogController.getSubtitleText(
+                mIsProgressBarVisible && !mIsSearchingHidden);
+    }
+
+    private Drawable getSignalStrengthDrawable() {
+        return mInternetDialogController.getSignalStrengthDrawable();
+    }
+
+    CharSequence getMobileNetworkTitle() {
+        return mInternetDialogController.getMobileNetworkTitle();
+    }
+
+    String getMobileNetworkSummary() {
+        return mInternetDialogController.getMobileNetworkSummary();
+    }
+
+    protected void showProgressBar() {
+        if (mWifiManager == null || !mWifiManager.isWifiEnabled()
+                || mInternetDialogController.isDeviceLocked()) {
+            setProgressBarVisible(false);
+            return;
+        }
+        setProgressBarVisible(true);
+        if (mConnectedWifiEntry != null || mWifiEntriesCount > 0) {
+            mHandler.postDelayed(mHideProgressBarRunnable, PROGRESS_DELAY_MS);
+        } else if (!mIsSearchingHidden) {
+            mHandler.postDelayed(mHideSearchingRunnable, PROGRESS_DELAY_MS);
+        }
+    }
+
+    private void setProgressBarVisible(boolean visible) {
+        if (mWifiManager.isWifiEnabled() && mAdapter.mHolderView != null
+                && mAdapter.mHolderView.isAttachedToWindow()) {
+            mIsProgressBarVisible = true;
+        }
+        mIsProgressBarVisible = visible;
+        mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.GONE);
+        mDivider.setVisibility(mIsProgressBarVisible ? View.GONE : View.VISIBLE);
+        mInternetDialogSubTitle.setText(getSubtitleText());
+    }
+
+    private boolean shouldShowMobileDialog() {
+        boolean flag = Prefs.getBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA,
+                false);
+        if (mInternetDialogController.isMobileDataEnabled() && !flag) {
+            return true;
+        }
+        return false;
+    }
+
+    private void showTurnOffMobileDialog() {
+        CharSequence carrierName = getMobileNetworkTitle();
+        boolean isInService = mInternetDialogController.isVoiceStateInService();
+        if (TextUtils.isEmpty(carrierName) || !isInService) {
+            carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier);
+        }
+        mAlertDialog = new Builder(mContext)
+                .setTitle(R.string.mobile_data_disable_title)
+                .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName))
+                .setNegativeButton(android.R.string.cancel, (d, w) -> {
+                    mMobileDataToggle.setChecked(true);
+                })
+                .setPositiveButton(
+                        com.android.internal.R.string.alert_windows_notification_turn_off_action,
+                        (d, w) -> {
+                            mInternetDialogController.setMobileDataEnabled(mContext,
+                                    mDefaultDataSubId, false, false);
+                            mMobileDataToggle.setChecked(false);
+                            Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true);
+                        })
+                .create();
+        mAlertDialog.setOnCancelListener(dialog -> mMobileDataToggle.setChecked(true));
+        mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+        SystemUIDialog.setShowForAllUsers(mAlertDialog, true);
+        SystemUIDialog.registerDismissListener(mAlertDialog);
+        SystemUIDialog.setWindowOnTop(mAlertDialog);
+        mAlertDialog.show();
+    }
+
+    @Override
+    public void onRefreshCarrierInfo() {
+        mHandler.post(() -> updateDialog());
+    }
+
+    @Override
+    public void onSimStateChanged() {
+        mHandler.post(() -> updateDialog());
+    }
+
+    @Override
+    public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
+        mHandler.post(() -> updateDialog());
+    }
+
+    @Override
+    public void onSubscriptionsChanged(int defaultDataSubId) {
+        mDefaultDataSubId = defaultDataSubId;
+        mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+        mHandler.post(() -> updateDialog());
+    }
+
+    @Override
+    public void onServiceStateChanged(ServiceState serviceState) {
+        mHandler.post(() -> updateDialog());
+    }
+
+    @Override
+    @WorkerThread
+    public void onDataConnectionStateChanged(int state, int networkType) {
+        mHandler.post(() -> updateDialog());
+    }
+
+    @Override
+    public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+        mHandler.post(() -> updateDialog());
+    }
+
+    @Override
+    public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
+        mHandler.post(() -> updateDialog());
+    }
+
+    @Override
+    @WorkerThread
+    public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
+            @Nullable WifiEntry connectedEntry) {
+        mConnectedWifiEntry = connectedEntry;
+        mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
+        mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount);
+        mHandler.post(() -> {
+            mAdapter.notifyDataSetChanged();
+            updateDialog();
+        });
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        if (mAlertDialog != null && !mAlertDialog.isShowing()) {
+            if (!hasFocus && isShowing()) {
+                dismiss();
+            }
+        }
+    }
+
+    public enum InternetDialogEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "The Internet dialog became visible on the screen.")
+        INTERNET_DIALOG_SHOW(843);
+
+        private final int mId;
+
+        InternetDialogEvent(int id) {
+            mId = id;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
new file mode 100644
index 0000000..50e7e43
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -0,0 +1,952 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog;
+
+import static com.android.settingslib.mobile.MobileMappings.getIconKey;
+import static com.android.settingslib.mobile.MobileMappings.mapIconSets;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.DeviceInfoUtils;
+import com.android.settingslib.SignalIcon;
+import com.android.settingslib.Utils;
+import com.android.settingslib.graph.SignalDrawable;
+import com.android.settingslib.mobile.MobileMappings;
+import com.android.settingslib.mobile.TelephonyIcons;
+import com.android.settingslib.net.SignalStrengthUtil;
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.wifitrackerlib.MergedCarrierEntry;
+import com.android.wifitrackerlib.WifiEntry;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+public class InternetDialogController implements WifiEntry.DisconnectCallback,
+        NetworkController.AccessPointController.AccessPointCallback {
+
+    private static final String TAG = "InternetDialogController";
+    private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
+            "android.settings.NETWORK_PROVIDER_SETTINGS";
+    private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
+    public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
+    public static final int NO_CELL_DATA_TYPE_ICON = 0;
+    private static final int SUBTITLE_TEXT_WIFI_IS_OFF = R.string.wifi_is_off;
+    private static final int SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT =
+            R.string.tap_a_network_to_connect;
+    private static final int SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS =
+            R.string.unlock_to_view_networks;
+    private static final int SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS =
+            R.string.wifi_empty_list_wifi_on;
+    private static final int SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE =
+            R.string.non_carrier_network_unavailable;
+    private static final int SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE =
+            R.string.all_network_unavailable;
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    static final int MAX_WIFI_ENTRY_COUNT = 4;
+
+    private WifiManager mWifiManager;
+    private Context mContext;
+    private SubscriptionManager mSubscriptionManager;
+    private TelephonyManager mTelephonyManager;
+    private ConnectivityManager mConnectivityManager;
+    private TelephonyDisplayInfo mTelephonyDisplayInfo =
+            new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+    private Handler mHandler;
+    private MobileMappings.Config mConfig = null;
+    private Executor mExecutor;
+    private AccessPointController mAccessPointController;
+    private IntentFilter mConnectionStateFilter;
+    private InternetDialogCallback mCallback;
+    private WifiEntry mConnectedEntry;
+    private int mWifiEntriesCount;
+    private UiEventLogger mUiEventLogger;
+    private BroadcastDispatcher mBroadcastDispatcher;
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private GlobalSettings mGlobalSettings;
+    private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+    @VisibleForTesting
+    protected ActivityStarter mActivityStarter;
+    @VisibleForTesting
+    protected SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
+    @VisibleForTesting
+    protected InternetTelephonyCallback mInternetTelephonyCallback;
+    @VisibleForTesting
+    protected WifiUtils.InternetIconInjector mWifiIconInjector;
+    @VisibleForTesting
+    protected boolean mCanConfigWifi;
+    @VisibleForTesting
+    protected KeyguardStateController mKeyguardStateController;
+
+    private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
+            new KeyguardUpdateMonitorCallback() {
+                @Override
+                public void onRefreshCarrierInfo() {
+                    mCallback.onRefreshCarrierInfo();
+                }
+
+                @Override
+                public void onSimStateChanged(int subId, int slotId, int simState) {
+                    mCallback.onSimStateChanged();
+                }
+            };
+
+    protected List<SubscriptionInfo> getSubscriptionInfo() {
+        return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
+    }
+
+    @Inject
+    public InternetDialogController(@NonNull Context context, UiEventLogger uiEventLogger,
+            ActivityStarter starter, AccessPointController accessPointController,
+            SubscriptionManager subscriptionManager, TelephonyManager telephonyManager,
+            @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
+            @Main Handler handler, @Main Executor mainExecutor,
+            BroadcastDispatcher broadcastDispatcher, KeyguardUpdateMonitor keyguardUpdateMonitor,
+            GlobalSettings globalSettings, KeyguardStateController keyguardStateController) {
+        if (DEBUG) {
+            Log.d(TAG, "Init InternetDialogController");
+        }
+        mHandler = handler;
+        mExecutor = mainExecutor;
+        mContext = context;
+        mGlobalSettings = globalSettings;
+        mWifiManager = wifiManager;
+        mTelephonyManager = telephonyManager;
+        mConnectivityManager = connectivityManager;
+        mSubscriptionManager = subscriptionManager;
+        mBroadcastDispatcher = broadcastDispatcher;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mKeyguardStateController = keyguardStateController;
+        mConnectionStateFilter = new IntentFilter();
+        mConnectionStateFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+        mUiEventLogger = uiEventLogger;
+        mActivityStarter = starter;
+        mAccessPointController = accessPointController;
+        mConfig = MobileMappings.Config.readConfig(mContext);
+        mWifiIconInjector = new WifiUtils.InternetIconInjector(mContext);
+    }
+
+    void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) {
+        if (DEBUG) {
+            Log.d(TAG, "onStart");
+        }
+        mCallback = callback;
+        mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
+        mAccessPointController.addAccessPointCallback(this);
+        mBroadcastDispatcher.registerReceiver(mConnectionStateReceiver, mConnectionStateFilter,
+                mExecutor);
+        // Listen the subscription changes
+        mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener();
+        mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
+                mOnSubscriptionsChangedListener);
+        mDefaultDataSubId = getDefaultDataSubscriptionId();
+        if (DEBUG) {
+            Log.d(TAG, "Init, SubId: " + mDefaultDataSubId);
+        }
+        mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+        mInternetTelephonyCallback = new InternetTelephonyCallback();
+        mTelephonyManager.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback);
+        // Listen the connectivity changes
+        mConnectivityManager.registerNetworkCallback(new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .build(), new DataConnectivityListener(), mHandler);
+        mCanConfigWifi = canConfigWifi;
+        scanWifiAccessPoints();
+    }
+
+    void onStop() {
+        if (DEBUG) {
+            Log.d(TAG, "onStop");
+        }
+        mBroadcastDispatcher.unregisterReceiver(mConnectionStateReceiver);
+        mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback);
+        mSubscriptionManager.removeOnSubscriptionsChangedListener(
+                mOnSubscriptionsChangedListener);
+        mAccessPointController.removeAccessPointCallback(this);
+        mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
+    }
+
+    @VisibleForTesting
+    boolean isAirplaneModeEnabled() {
+        return mGlobalSettings.getInt(Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+    }
+
+    @VisibleForTesting
+    protected int getDefaultDataSubscriptionId() {
+        return mSubscriptionManager.getDefaultDataSubscriptionId();
+    }
+
+    @VisibleForTesting
+    protected Intent getSettingsIntent() {
+        return new Intent(ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    }
+
+    protected Intent getWifiDetailsSettingsIntent(String key) {
+        if (TextUtils.isEmpty(key)) {
+            if (DEBUG) {
+                Log.d(TAG, "connected entry's key is empty");
+            }
+            return null;
+        }
+        return WifiUtils.getWifiDetailsSettingsIntent(key);
+    }
+
+    CharSequence getDialogTitleText() {
+        if (isAirplaneModeEnabled()) {
+            return mContext.getText(R.string.airplane_mode);
+        }
+        return mContext.getText(R.string.quick_settings_internet_label);
+    }
+
+    CharSequence getSubtitleText(boolean isProgressBarVisible) {
+        if (isAirplaneModeEnabled()) {
+            return null;
+        }
+
+        if (mCanConfigWifi && !mWifiManager.isWifiEnabled()) {
+            // When the airplane mode is off and Wi-Fi is disabled.
+            //   Sub-Title: Wi-Fi is off
+            if (DEBUG) {
+                Log.d(TAG, "Airplane mode off + Wi-Fi off.");
+            }
+            return mContext.getText(SUBTITLE_TEXT_WIFI_IS_OFF);
+        }
+
+        if (isDeviceLocked()) {
+            // When the device is locked.
+            //   Sub-Title: Unlock to view networks
+            if (DEBUG) {
+                Log.d(TAG, "The device is locked.");
+            }
+            return mContext.getText(SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS);
+        }
+
+        if (mConnectedEntry != null || mWifiEntriesCount > 0) {
+            return mCanConfigWifi ? mContext.getText(SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT) : null;
+        }
+
+        if (mCanConfigWifi && isProgressBarVisible) {
+            // When the Wi-Fi scan result callback is received
+            //   Sub-Title: Searching for networks...
+            return mContext.getText(SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS);
+        }
+
+        // Sub-Title:
+        // show non_carrier_network_unavailable
+        //   - while Wi-Fi on + no Wi-Fi item
+        //   - while Wi-Fi on + no Wi-Fi item + mobile data off
+        // show all_network_unavailable:
+        //   - while Wi-Fi on + no Wi-Fi item + no carrier item
+        //   - while Wi-Fi on + no Wi-Fi item + service is out of service
+        //   - while Wi-Fi on + no Wi-Fi item + mobile data on + no carrier data.
+        if (DEBUG) {
+            Log.d(TAG, "No Wi-Fi item.");
+        }
+        if (!hasCarrier() || (!isVoiceStateInService() && !isDataStateInService())) {
+            if (DEBUG) {
+                Log.d(TAG, "No carrier or service is out of service.");
+            }
+            return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE);
+        }
+
+        if (mCanConfigWifi && !isMobileDataEnabled()) {
+            if (DEBUG) {
+                Log.d(TAG, "Mobile data off");
+            }
+            return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE);
+        }
+
+        if (!activeNetworkIsCellular()) {
+            if (DEBUG) {
+                Log.d(TAG, "No carrier data.");
+            }
+            return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE);
+        }
+
+        if (mCanConfigWifi) {
+            return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE);
+        }
+        return null;
+    }
+
+    Drawable getInternetWifiDrawable(@NonNull WifiEntry wifiEntry) {
+        if (wifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
+            return null;
+        }
+        final Drawable drawable =
+                mWifiIconInjector.getIcon(wifiEntry.shouldShowXLevelIcon(), wifiEntry.getLevel());
+        if (drawable == null) {
+            return null;
+        }
+        drawable.setTint(mContext.getColor(R.color.connected_network_primary_color));
+        return drawable;
+    }
+
+    boolean isNightMode() {
+        return (mContext.getResources().getConfiguration().uiMode
+                & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+    }
+
+    Drawable getSignalStrengthDrawable() {
+        Drawable drawable = mContext.getDrawable(
+                R.drawable.ic_signal_strength_zero_bar_no_internet);
+        try {
+            if (mTelephonyManager == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "TelephonyManager is null");
+                }
+                return drawable;
+            }
+
+            if (isDataStateInService() || isVoiceStateInService()) {
+                AtomicReference<Drawable> shared = new AtomicReference<>();
+                shared.set(getSignalStrengthDrawableWithLevel());
+                drawable = shared.get();
+            }
+
+            drawable.setTint(activeNetworkIsCellular() ? mContext.getColor(
+                    R.color.connected_network_primary_color) : Utils.getColorAttrDefaultColor(
+                    mContext, android.R.attr.textColorTertiary));
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+        return drawable;
+    }
+
+    /**
+     * To get the signal bar icon with level.
+     *
+     * @return The Drawable which is a signal bar icon with level.
+     */
+    Drawable getSignalStrengthDrawableWithLevel() {
+        final SignalStrength strength = mTelephonyManager.getSignalStrength();
+        int level = (strength == null) ? 0 : strength.getLevel();
+        int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
+        if (mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId)) {
+            level += 1;
+            numLevels += 1;
+        }
+        return getSignalStrengthIcon(mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON,
+                !isMobileDataEnabled());
+    }
+
+    Drawable getSignalStrengthIcon(Context context, int level, int numLevels,
+            int iconType, boolean cutOut) {
+        Log.d(TAG, "getSignalStrengthIcon");
+        final SignalDrawable signalDrawable = new SignalDrawable(context);
+        signalDrawable.setLevel(
+                SignalDrawable.getState(level, numLevels, cutOut));
+
+        // Make the network type drawable
+        final Drawable networkDrawable =
+                iconType == NO_CELL_DATA_TYPE_ICON
+                        ? EMPTY_DRAWABLE
+                        : context.getResources().getDrawable(iconType, context.getTheme());
+
+        // Overlay the two drawables
+        final Drawable[] layers = {networkDrawable, signalDrawable};
+        final int iconSize =
+                context.getResources().getDimensionPixelSize(R.dimen.signal_strength_icon_size);
+
+        final LayerDrawable icons = new LayerDrawable(layers);
+        // Set the network type icon at the top left
+        icons.setLayerGravity(0 /* index of networkDrawable */, Gravity.TOP | Gravity.LEFT);
+        // Set the signal strength icon at the bottom right
+        icons.setLayerGravity(1 /* index of SignalDrawable */, Gravity.BOTTOM | Gravity.RIGHT);
+        icons.setLayerSize(1 /* index of SignalDrawable */, iconSize, iconSize);
+        icons.setTintList(Utils.getColorAttr(context, android.R.attr.textColorTertiary));
+        return icons;
+    }
+
+    private boolean shouldInflateSignalStrength(int subId) {
+        return SignalStrengthUtil.shouldInflateSignalStrength(mContext, subId);
+    }
+
+    private CharSequence getUniqueSubscriptionDisplayName(int subscriptionId, Context context) {
+        final Map<Integer, CharSequence> displayNames = getUniqueSubscriptionDisplayNames(context);
+        return displayNames.getOrDefault(subscriptionId, "");
+    }
+
+    private Map<Integer, CharSequence> getUniqueSubscriptionDisplayNames(Context context) {
+        class DisplayInfo {
+            public SubscriptionInfo subscriptionInfo;
+            public CharSequence originalName;
+            public CharSequence uniqueName;
+        }
+
+        // Map of SubscriptionId to DisplayName
+        final Supplier<Stream<DisplayInfo>> originalInfos =
+                () -> getSubscriptionInfo()
+                        .stream()
+                        .filter(i -> {
+                            // Filter out null values.
+                            return (i != null && i.getDisplayName() != null);
+                        })
+                        .map(i -> {
+                            DisplayInfo info = new DisplayInfo();
+                            info.subscriptionInfo = i;
+                            info.originalName = i.getDisplayName().toString().trim();
+                            return info;
+                        });
+
+        // A Unique set of display names
+        Set<CharSequence> uniqueNames = new HashSet<>();
+        // Return the set of duplicate names
+        final Set<CharSequence> duplicateOriginalNames = originalInfos.get()
+                .filter(info -> !uniqueNames.add(info.originalName))
+                .map(info -> info.originalName)
+                .collect(Collectors.toSet());
+
+        // If a display name is duplicate, append the final 4 digits of the phone number.
+        // Creates a mapping of Subscription id to original display name + phone number display name
+        final Supplier<Stream<DisplayInfo>> uniqueInfos = () -> originalInfos.get().map(info -> {
+            if (duplicateOriginalNames.contains(info.originalName)) {
+                // This may return null, if the user cannot view the phone number itself.
+                final String phoneNumber = DeviceInfoUtils.getBidiFormattedPhoneNumber(context,
+                        info.subscriptionInfo);
+                String lastFourDigits = "";
+                if (phoneNumber != null) {
+                    lastFourDigits = (phoneNumber.length() > 4)
+                            ? phoneNumber.substring(phoneNumber.length() - 4) : phoneNumber;
+                }
+
+                if (TextUtils.isEmpty(lastFourDigits)) {
+                    info.uniqueName = info.originalName;
+                } else {
+                    info.uniqueName = info.originalName + " " + lastFourDigits;
+                }
+
+            } else {
+                info.uniqueName = info.originalName;
+            }
+            return info;
+        });
+
+        // Check uniqueness a second time.
+        // We might not have had permission to view the phone numbers.
+        // There might also be multiple phone numbers whose last 4 digits the same.
+        uniqueNames.clear();
+        final Set<CharSequence> duplicatePhoneNames = uniqueInfos.get()
+                .filter(info -> !uniqueNames.add(info.uniqueName))
+                .map(info -> info.uniqueName)
+                .collect(Collectors.toSet());
+
+        return uniqueInfos.get().map(info -> {
+            if (duplicatePhoneNames.contains(info.uniqueName)) {
+                info.uniqueName = info.originalName + " "
+                        + info.subscriptionInfo.getSubscriptionId();
+            }
+            return info;
+        }).collect(Collectors.toMap(
+                info -> info.subscriptionInfo.getSubscriptionId(),
+                info -> info.uniqueName));
+    }
+
+    CharSequence getMobileNetworkTitle() {
+        return getUniqueSubscriptionDisplayName(mDefaultDataSubId, mContext);
+    }
+
+    String getMobileNetworkSummary() {
+        String description = getNetworkTypeDescription(mContext, mConfig,
+                mTelephonyDisplayInfo, mDefaultDataSubId);
+        return getMobileSummary(mContext, mTelephonyManager, description);
+    }
+
+    /**
+     * Get currently description of mobile network type.
+     */
+    private String getNetworkTypeDescription(Context context, MobileMappings.Config config,
+            TelephonyDisplayInfo telephonyDisplayInfo, int subId) {
+        String iconKey = getIconKey(telephonyDisplayInfo);
+
+        if (mapIconSets(config) == null || mapIconSets(config).get(iconKey) == null) {
+            if (DEBUG) {
+                Log.d(TAG, "The description of network type is empty.");
+            }
+            return "";
+        }
+
+        int resId = mapIconSets(config).get(iconKey).dataContentDescription;
+        final MergedCarrierEntry mergedCarrierEntry =
+                mAccessPointController.getMergedCarrierEntry();
+        if (mergedCarrierEntry != null && mergedCarrierEntry.isDefaultNetwork()) {
+            SignalIcon.MobileIconGroup carrierMergedWifiIconGroup =
+                    TelephonyIcons.CARRIER_MERGED_WIFI;
+            resId = carrierMergedWifiIconGroup.dataContentDescription;
+        }
+
+        return resId != 0
+                ? SubscriptionManager.getResourcesForSubId(context, subId).getString(resId) : "";
+    }
+
+    private String getMobileSummary(Context context, TelephonyManager telephonyManager,
+            String networkTypeDescription) {
+        if (!isMobileDataEnabled()) {
+            return context.getString(R.string.mobile_data_off_summary);
+        }
+        if (!isDataStateInService()) {
+            return context.getString(R.string.mobile_data_no_connection);
+        }
+        String summary = networkTypeDescription;
+        if (activeNetworkIsCellular()) {
+            summary = context.getString(R.string.preference_summary_default_combination,
+                    context.getString(R.string.mobile_data_connection_active),
+                    networkTypeDescription);
+        }
+        return summary;
+    }
+
+    void launchNetworkSetting() {
+        mCallback.dismissDialog();
+        mActivityStarter.postStartActivityDismissingKeyguard(getSettingsIntent(), 0);
+    }
+
+    void launchWifiNetworkDetailsSetting(String key) {
+        Intent intent = getWifiDetailsSettingsIntent(key);
+        if (intent != null) {
+            mCallback.dismissDialog();
+            mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+        }
+    }
+
+    void connectCarrierNetwork() {
+        final MergedCarrierEntry mergedCarrierEntry =
+                mAccessPointController.getMergedCarrierEntry();
+        if (mergedCarrierEntry != null && mergedCarrierEntry.canConnect()) {
+            mergedCarrierEntry.connect(null /* ConnectCallback */);
+        }
+    }
+
+    WifiManager getWifiManager() {
+        return mWifiManager;
+    }
+
+    TelephonyManager getTelephonyManager() {
+        return mTelephonyManager;
+    }
+
+    SubscriptionManager getSubscriptionManager() {
+        return mSubscriptionManager;
+    }
+
+    /**
+     * @return whether there is the carrier item in the slice.
+     */
+    boolean hasCarrier() {
+        if (mSubscriptionManager == null) {
+            if (DEBUG) {
+                Log.d(TAG, "SubscriptionManager is null, can not check carrier.");
+            }
+            return false;
+        }
+
+        if (isAirplaneModeEnabled() || mTelephonyManager == null
+                || mSubscriptionManager.getActiveSubscriptionIdList().length <= 0) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Return {@code true} if mobile data is enabled
+     */
+    boolean isMobileDataEnabled() {
+        if (mTelephonyManager == null || !mTelephonyManager.isDataEnabled()) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Set whether to enable data for {@code subId}, also whether to disable data for other
+     * subscription
+     */
+    void setMobileDataEnabled(Context context, int subId, boolean enabled,
+            boolean disableOtherSubscriptions) {
+        if (mTelephonyManager == null) {
+            if (DEBUG) {
+                Log.d(TAG, "TelephonyManager is null, can not set mobile data.");
+            }
+            return;
+        }
+
+        if (mSubscriptionManager == null) {
+            if (DEBUG) {
+                Log.d(TAG, "SubscriptionManager is null, can not set mobile data.");
+            }
+            return;
+        }
+
+        mTelephonyManager.setDataEnabled(enabled);
+        if (disableOtherSubscriptions) {
+            final List<SubscriptionInfo> subInfoList =
+                    mSubscriptionManager.getActiveSubscriptionInfoList();
+            if (subInfoList != null) {
+                for (SubscriptionInfo subInfo : subInfoList) {
+                    // We never disable mobile data for opportunistic subscriptions.
+                    if (subInfo.getSubscriptionId() != subId && !subInfo.isOpportunistic()) {
+                        context.getSystemService(TelephonyManager.class).createForSubscriptionId(
+                                subInfo.getSubscriptionId()).setDataEnabled(false);
+                    }
+                }
+            }
+        }
+    }
+
+    boolean isDataStateInService() {
+        final ServiceState serviceState = mTelephonyManager.getServiceState();
+        NetworkRegistrationInfo regInfo =
+                (serviceState == null) ? null : serviceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        return (regInfo == null) ? false : regInfo.isRegistered();
+    }
+
+    boolean isVoiceStateInService() {
+        if (mTelephonyManager == null) {
+            if (DEBUG) {
+                Log.d(TAG, "TelephonyManager is null, can not detect voice state.");
+            }
+            return false;
+        }
+
+        final ServiceState serviceState = mTelephonyManager.getServiceState();
+        return serviceState != null
+                && serviceState.getState() == serviceState.STATE_IN_SERVICE;
+    }
+
+    public boolean isDeviceLocked() {
+        return !mKeyguardStateController.isUnlocked();
+    }
+
+    boolean activeNetworkIsCellular() {
+        if (mConnectivityManager == null) {
+            if (DEBUG) {
+                Log.d(TAG, "ConnectivityManager is null, can not check active network.");
+            }
+            return false;
+        }
+
+        final Network activeNetwork = mConnectivityManager.getActiveNetwork();
+        if (activeNetwork == null) {
+            return false;
+        }
+        final NetworkCapabilities networkCapabilities =
+                mConnectivityManager.getNetworkCapabilities(activeNetwork);
+        if (networkCapabilities == null) {
+            return false;
+        }
+        return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
+    }
+
+    boolean connect(WifiEntry ap) {
+        if (ap == null) {
+            if (DEBUG) {
+                Log.d(TAG, "No Wi-Fi ap to connect.");
+            }
+            return false;
+        }
+
+        if (ap.getWifiConfiguration() != null) {
+            if (DEBUG) {
+                Log.d(TAG, "connect networkId=" + ap.getWifiConfiguration().networkId);
+            }
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "connect to unsaved network " + ap.getTitle());
+            }
+        }
+        ap.connect(new WifiEntryConnectCallback(mActivityStarter, mContext, ap));
+        return false;
+    }
+
+    static class WifiEntryConnectCallback implements WifiEntry.ConnectCallback {
+        final ActivityStarter mActivityStarter;
+        final Context mContext;
+        final WifiEntry mWifiEntry;
+
+        WifiEntryConnectCallback(ActivityStarter activityStarter, Context context,
+                WifiEntry connectWifiEntry) {
+            mActivityStarter = activityStarter;
+            mContext = context;
+            mWifiEntry = connectWifiEntry;
+        }
+
+        @Override
+        public void onConnectResult(@ConnectStatus int status) {
+            if (DEBUG) {
+                Log.d(TAG, "onConnectResult " + status);
+            }
+
+            if (status == WifiEntry.ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) {
+                final Intent intent = new Intent("com.android.settings.WIFI_DIALOG")
+                        .putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, mWifiEntry.getKey());
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                mActivityStarter.startActivity(intent, false /* dismissShade */);
+            } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) {
+                Toast.makeText(mContext, R.string.wifi_failed_connect_message,
+                        Toast.LENGTH_SHORT).show();
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "connect failure reason=" + status);
+                }
+            }
+        }
+    }
+
+    private void scanWifiAccessPoints() {
+        if (mCanConfigWifi) {
+            mAccessPointController.scanForAccessPoints();
+        }
+    }
+
+    @Override
+    @WorkerThread
+    public void onAccessPointsChanged(List<WifiEntry> accessPoints) {
+        if (!mCanConfigWifi) {
+            return;
+        }
+
+        if (accessPoints == null || accessPoints.size() == 0) {
+            mConnectedEntry = null;
+            mWifiEntriesCount = 0;
+            if (mCallback != null) {
+                mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */);
+            }
+            return;
+        }
+
+        boolean hasConnectedWifi = false;
+        final int accessPointSize = accessPoints.size();
+        for (int i = 0; i < accessPointSize; i++) {
+            WifiEntry wifiEntry = accessPoints.get(i);
+            if (wifiEntry.isDefaultNetwork() && wifiEntry.hasInternetAccess()) {
+                mConnectedEntry = wifiEntry;
+                hasConnectedWifi = true;
+                break;
+            }
+        }
+        if (!hasConnectedWifi) {
+            mConnectedEntry = null;
+        }
+
+        int count = MAX_WIFI_ENTRY_COUNT;
+        if (hasCarrier()) {
+            count -= 1;
+        }
+        if (hasConnectedWifi) {
+            count -= 1;
+        }
+        final List<WifiEntry> wifiEntries = accessPoints.stream()
+                .filter(wifiEntry -> (!wifiEntry.isDefaultNetwork()
+                        || !wifiEntry.hasInternetAccess()))
+                .limit(count)
+                .collect(Collectors.toList());
+        mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
+
+        if (mCallback != null) {
+            mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry);
+        }
+    }
+
+    @Override
+    public void onSettingsActivityTriggered(Intent settingsIntent) {
+    }
+
+    @Override
+    public void onDisconnectResult(int status) {
+    }
+
+    private class InternetTelephonyCallback extends TelephonyCallback implements
+            TelephonyCallback.DataConnectionStateListener,
+            TelephonyCallback.DisplayInfoListener,
+            TelephonyCallback.ServiceStateListener,
+            TelephonyCallback.SignalStrengthsListener {
+
+        @Override
+        public void onServiceStateChanged(@NonNull ServiceState serviceState) {
+            mCallback.onServiceStateChanged(serviceState);
+        }
+
+        @Override
+        public void onDataConnectionStateChanged(int state, int networkType) {
+            mCallback.onDataConnectionStateChanged(state, networkType);
+        }
+
+        @Override
+        public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength) {
+            mCallback.onSignalStrengthsChanged(signalStrength);
+        }
+
+        @Override
+        public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
+            mTelephonyDisplayInfo = telephonyDisplayInfo;
+            mCallback.onDisplayInfoChanged(telephonyDisplayInfo);
+        }
+    }
+
+    private class InternetOnSubscriptionChangedListener
+            extends SubscriptionManager.OnSubscriptionsChangedListener {
+        InternetOnSubscriptionChangedListener() {
+            super();
+        }
+
+        @Override
+        public void onSubscriptionsChanged() {
+            updateListener();
+        }
+    }
+
+    private class DataConnectivityListener extends ConnectivityManager.NetworkCallback {
+        @Override
+        @WorkerThread
+        public void onCapabilitiesChanged(@NonNull Network network,
+                @NonNull NetworkCapabilities networkCapabilities) {
+            if (mCanConfigWifi) {
+                for (int transport : networkCapabilities.getTransportTypes()) {
+                    if (transport == NetworkCapabilities.TRANSPORT_WIFI) {
+                        scanWifiAccessPoints();
+                        break;
+                    }
+                }
+            }
+            final Network activeNetwork = mConnectivityManager.getActiveNetwork();
+            if (activeNetwork != null && activeNetwork.equals(network)) {
+                // update UI
+                mCallback.onCapabilitiesChanged(network, networkCapabilities);
+            }
+        }
+    }
+
+    private final BroadcastReceiver mConnectionStateReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (action.equals(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
+                if (DEBUG) {
+                    Log.d(TAG, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED");
+                }
+                updateListener();
+            }
+        }
+    };
+
+    private void updateListener() {
+        int defaultDataSubId = getDefaultDataSubscriptionId();
+        if (mDefaultDataSubId == getDefaultDataSubscriptionId()) {
+            if (DEBUG) {
+                Log.d(TAG, "DDS: no change");
+            }
+            return;
+        }
+
+        mDefaultDataSubId = defaultDataSubId;
+        if (DEBUG) {
+            Log.d(TAG, "DDS: defaultDataSubId:" + mDefaultDataSubId);
+        }
+        if (SubscriptionManager.isUsableSubscriptionId(mDefaultDataSubId)) {
+            mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback);
+            mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+            mTelephonyManager.registerTelephonyCallback(mHandler::post,
+                    mInternetTelephonyCallback);
+            mCallback.onSubscriptionsChanged(mDefaultDataSubId);
+        }
+    }
+
+    public WifiUtils.InternetIconInjector getWifiIconInjector() {
+        return mWifiIconInjector;
+    }
+
+    interface InternetDialogCallback {
+
+        void onRefreshCarrierInfo();
+
+        void onSimStateChanged();
+
+        void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities);
+
+        void onSubscriptionsChanged(int defaultDataSubId);
+
+        void onServiceStateChanged(ServiceState serviceState);
+
+        void onDataConnectionStateChanged(int state, int networkType);
+
+        void onSignalStrengthsChanged(SignalStrength signalStrength);
+
+        void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo);
+
+        void dismissDialog();
+
+        void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
+                @Nullable WifiEntry connectedEntry);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
new file mode 100644
index 0000000..11c6980
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.tiles.dialog
+
+import android.content.Context
+import android.os.Handler
+import android.util.Log
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import javax.inject.Inject
+
+private const val TAG = "InternetDialogFactory"
+private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+/**
+ * Factory to create [InternetDialog] objects.
+ */
+@SysUISingleton
+class InternetDialogFactory @Inject constructor(
+    @Main private val handler: Handler,
+    private val internetDialogController: InternetDialogController,
+    private val context: Context,
+    private val uiEventLogger: UiEventLogger
+) {
+    companion object {
+        var internetDialog: InternetDialog? = null
+    }
+
+    /** Creates a [InternetDialog]. */
+    fun create(aboveStatusBar: Boolean, canConfigMobileData: Boolean, canConfigWifi: Boolean) {
+        if (internetDialog != null) {
+            if (DEBUG) {
+                Log.d(TAG, "InternetDialog is showing, do not create it twice.")
+            }
+            return
+        } else {
+            internetDialog = InternetDialog(context, this, internetDialogController,
+                    canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler)
+            internetDialog?.show()
+        }
+    }
+
+    fun destroyDialog() {
+        if (DEBUG) {
+            Log.d(TAG, "destroyDialog")
+        }
+        internetDialog = null
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java
new file mode 100644
index 0000000..6aaba99
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java
@@ -0,0 +1,14 @@
+package com.android.systemui.qs.tiles.dialog;
+
+import android.content.Context;
+import android.util.FeatureFlagUtils;
+
+public class InternetDialogUtil {
+
+    public static boolean isProviderModelEnabled(Context context) {
+        if (context == null) {
+            return false;
+        }
+        return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 0eaef72..31d51f1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -154,6 +154,7 @@
     @Override
     public void onStart() {
         super.onStart();
+        mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_STARTED);
 
         if (mPreview.getDrawable() != null) {
             // We already have an image, so no need to try to load again.
@@ -245,6 +246,8 @@
     }
 
     private void onCachedImageLoaded(ImageLoader.Result imageResult) {
+        mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED);
+
         BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap);
         mPreview.setImageDrawable(drawable);
         mPreview.setAlpha(1f);
@@ -282,6 +285,8 @@
             finish();
         }
         if (isFinishing()) {
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_FINISHED);
+
             if (mScrollCaptureResponse != null) {
                 mScrollCaptureResponse.close();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 16872b0..8def475 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -33,6 +33,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.annotation.MainThread;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -261,6 +262,7 @@
     private Bitmap mScreenBitmap;
     private SaveImageInBackgroundTask mSaveInBgTask;
     private boolean mScreenshotTakenInPortrait;
+    private boolean mBlockAttach;
 
     private Animator mScreenshotAnimation;
     private RequestCallback mCurrentRequestCallback;
@@ -559,8 +561,8 @@
             mScreenshotView.reset();
         }
 
-        mScreenshotView.updateOrientation(mWindowManager.getCurrentWindowMetrics()
-                .getWindowInsets().getDisplayCutout());
+        mScreenshotView.updateOrientation(
+                mWindowManager.getCurrentWindowMetrics().getWindowInsets());
 
         mScreenBitmap = screenshot;
 
@@ -594,9 +596,8 @@
                             // Delay scroll capture eval a bit to allow the underlying activity
                             // to set up in the new orientation.
                             mScreenshotHandler.postDelayed(this::requestScrollCapture, 150);
-                            mScreenshotView.updateDisplayCutoutMargins(
-                                    mWindowManager.getCurrentWindowMetrics().getWindowInsets()
-                                            .getDisplayCutout());
+                            mScreenshotView.updateInsets(
+                                    mWindowManager.getCurrentWindowMetrics().getWindowInsets());
                             // screenshot animation calculations won't be valid anymore, so just end
                             if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
                                 mScreenshotAnimation.end();
@@ -660,7 +661,7 @@
                     + mLastScrollCaptureResponse.getWindowTitle() + "]");
 
             final ScrollCaptureResponse response = mLastScrollCaptureResponse;
-            mScreenshotView.showScrollChip(/* onClick */ () -> {
+            mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
                 DisplayMetrics displayMetrics = new DisplayMetrics();
                 getDefaultDisplay().getRealMetrics(displayMetrics);
                 Bitmap newScreenshot = captureScreenshot(
@@ -732,6 +733,7 @@
                     new ViewTreeObserver.OnWindowAttachListener() {
                         @Override
                         public void onWindowAttached() {
+                            mBlockAttach = false;
                             decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
                             action.run();
                         }
@@ -748,14 +750,16 @@
         mWindow.setContentView(contentView);
     }
 
+    @MainThread
     private void attachWindow() {
         View decorView = mWindow.getDecorView();
-        if (decorView.isAttachedToWindow()) {
+        if (decorView.isAttachedToWindow() || mBlockAttach) {
             return;
         }
         if (DEBUG_WINDOW) {
             Log.d(TAG, "attachWindow");
         }
+        mBlockAttach = true;
         mWindowManager.addView(decorView, mWindowLayoutParams);
         decorView.requestApplyInsets();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index 5cf0188..169b28c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -71,7 +71,19 @@
     @UiEvent(doc = "User has shared a long screenshot")
     SCREENSHOT_LONG_SCREENSHOT_SHARE(689),
     @UiEvent(doc = "User has sent a long screenshot to the editor")
-    SCREENSHOT_LONG_SCREENSHOT_EDIT(690);
+    SCREENSHOT_LONG_SCREENSHOT_EDIT(690),
+    @UiEvent(doc = "A long screenshot capture has started")
+    SCREENSHOT_LONG_SCREENSHOT_STARTED(880),
+    @UiEvent(doc = "The long screenshot capture failed")
+    SCREENSHOT_LONG_SCREENSHOT_FAILURE(881),
+    @UiEvent(doc = "The long screenshot capture completed successfully")
+    SCREENSHOT_LONG_SCREENSHOT_COMPLETED(882),
+    @UiEvent(doc = "Long screenshot editor activity started")
+    SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_STARTED(889),
+    @UiEvent(doc = "Long screenshot editor activity loaded a previously saved screenshot")
+    SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED(890),
+    @UiEvent(doc = "Long screenshot editor activity finished")
+    SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_FINISHED(891);
 
     private final int mId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index e9e62f2..dfb39e3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -118,8 +118,8 @@
     private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
     private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
     private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
-    private static final long SCREENSHOT_DISMISS_Y_DURATION_MS = 350;
-    private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 183;
+    private static final long SCREENSHOT_DISMISS_X_DURATION_MS = 350;
+    private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 350;
     private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade
     private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
     private static final float ROUNDED_CORNER_RADIUS = .25f;
@@ -242,19 +242,21 @@
     /**
      * Called to display the scroll action chip when support is detected.
      *
+     * @param packageName the owning package of the window to be captured
      * @param onClick the action to take when the chip is clicked.
      */
-    public void showScrollChip(Runnable onClick) {
+    public void showScrollChip(String packageName, Runnable onClick) {
         if (DEBUG_SCROLL) {
             Log.d(TAG, "Showing Scroll option");
         }
-        mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION);
+        mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, packageName);
         mScrollChip.setVisibility(VISIBLE);
         mScrollChip.setOnClickListener((v) -> {
             if (DEBUG_INPUT) {
                 Log.d(TAG, "scroll chip tapped");
             }
-            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED);
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
+                    packageName);
             onClick.run();
         });
     }
@@ -414,21 +416,30 @@
         mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
     }
 
-    void updateDisplayCutoutMargins(DisplayCutout cutout) {
+    void updateInsets(WindowInsets insets) {
         int orientation = mContext.getResources().getConfiguration().orientation;
         mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
         FrameLayout.LayoutParams p =
                 (FrameLayout.LayoutParams) mScreenshotStatic.getLayoutParams();
+        DisplayCutout cutout = insets.getDisplayCutout();
+        Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
         if (cutout == null) {
-            p.setMargins(0, 0, 0, 0);
+            p.setMargins(0, 0, 0, navBarInsets.bottom);
         } else {
             Insets waterfall = cutout.getWaterfallInsets();
             if (mOrientationPortrait) {
-                p.setMargins(waterfall.left, Math.max(cutout.getSafeInsetTop(), waterfall.top),
-                        waterfall.right, Math.max(cutout.getSafeInsetBottom(), waterfall.bottom));
+                p.setMargins(
+                        waterfall.left,
+                        Math.max(cutout.getSafeInsetTop(), waterfall.top),
+                        waterfall.right,
+                        Math.max(cutout.getSafeInsetBottom(),
+                                Math.max(navBarInsets.bottom, waterfall.bottom)));
             } else {
-                p.setMargins(Math.max(cutout.getSafeInsetLeft(), waterfall.left), waterfall.top,
-                        Math.max(cutout.getSafeInsetRight(), waterfall.right), waterfall.bottom);
+                p.setMargins(
+                        Math.max(cutout.getSafeInsetLeft(), waterfall.left),
+                        waterfall.top,
+                        Math.max(cutout.getSafeInsetRight(), waterfall.right),
+                        Math.max(navBarInsets.bottom, waterfall.bottom));
             }
         }
         mStaticLeftMargin = p.leftMargin;
@@ -436,10 +447,10 @@
         mScreenshotStatic.requestLayout();
     }
 
-    void updateOrientation(DisplayCutout cutout) {
+    void updateOrientation(WindowInsets insets) {
         int orientation = mContext.getResources().getConfiguration().orientation;
         mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
-        updateDisplayCutoutMargins(cutout);
+        updateInsets(insets);
         int screenshotFixedSize =
                 mContext.getResources().getDimensionPixelSize(R.dimen.global_screenshot_x_scale);
         ViewGroup.LayoutParams params = mScreenshotPreview.getLayoutParams();
@@ -978,7 +989,6 @@
         mScrollingScrim.setVisibility(View.GONE);
         mScrollablePreview.setVisibility(View.GONE);
         mScreenshotStatic.setTranslationX(0);
-        mScreenshotPreview.setTranslationY(0);
         mScreenshotPreview.setContentDescription(
                 mContext.getResources().getString(R.string.screenshot_preview_description));
         mScreenshotPreview.setOnClickListener(null);
@@ -994,9 +1004,6 @@
         mSmartChips.clear();
         mQuickShareChip = null;
         setAlpha(1);
-        mDismissButton.setTranslationY(0);
-        mActionsContainer.setTranslationY(0);
-        mActionsContainerBackground.setTranslationY(0);
         mScreenshotSelectorView.stop();
     }
 
@@ -1024,22 +1031,19 @@
             setAlpha(1 - animation.getAnimatedFraction());
         });
 
-        ValueAnimator yAnim = ValueAnimator.ofFloat(0, 1);
-        yAnim.setInterpolator(mAccelerateInterpolator);
-        yAnim.setDuration(SCREENSHOT_DISMISS_Y_DURATION_MS);
-        float screenshotStartY = mScreenshotPreview.getTranslationY();
-        float dismissStartY = mDismissButton.getTranslationY();
-        yAnim.addUpdateListener(animation -> {
-            float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction());
-            mScreenshotPreview.setTranslationY(screenshotStartY + yDelta);
-            mScreenshotPreviewBorder.setTranslationY(screenshotStartY + yDelta);
-            mDismissButton.setTranslationY(dismissStartY + yDelta);
-            mActionsContainer.setTranslationY(yDelta);
-            mActionsContainerBackground.setTranslationY(yDelta);
+        ValueAnimator xAnim = ValueAnimator.ofFloat(0, 1);
+        xAnim.setInterpolator(mAccelerateInterpolator);
+        xAnim.setDuration(SCREENSHOT_DISMISS_X_DURATION_MS);
+        float deltaX = mDirectionLTR
+                    ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth())
+                    : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX());
+        xAnim.addUpdateListener(animation -> {
+            float currXDelta = MathUtils.lerp(0, deltaX, animation.getAnimatedFraction());
+            mScreenshotStatic.setTranslationX(currXDelta);
         });
 
         AnimatorSet animSet = new AnimatorSet();
-        animSet.play(yAnim).with(alphaAnim);
+        animSet.play(xAnim).with(alphaAnim);
 
         return animSet;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 6dc6874..ef7355a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -28,6 +28,7 @@
 import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
 import com.android.systemui.screenshot.ScrollCaptureClient.Session;
@@ -61,6 +62,7 @@
     private final Context mContext;
     private final Executor mBgExecutor;
     private final ImageTileSet mImageTileSet;
+    private final UiEventLogger mEventLogger;
     private final ScrollCaptureClient mClient;
 
     private Completer<LongScreenshot> mCaptureCompleter;
@@ -69,6 +71,7 @@
     private Session mSession;
     private ListenableFuture<CaptureResult> mTileFuture;
     private ListenableFuture<Void> mEndFuture;
+    private String mWindowOwner;
 
     static class LongScreenshot {
         private final ImageTileSet mImageTileSet;
@@ -135,11 +138,12 @@
 
     @Inject
     ScrollCaptureController(Context context, @Background Executor bgExecutor,
-            ScrollCaptureClient client, ImageTileSet imageTileSet) {
+            ScrollCaptureClient client, ImageTileSet imageTileSet, UiEventLogger logger) {
         mContext = context;
         mBgExecutor = bgExecutor;
         mClient = client;
         mImageTileSet = imageTileSet;
+        mEventLogger = logger;
     }
 
     @VisibleForTesting
@@ -157,6 +161,7 @@
     ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) {
         return CallbackToFutureAdapter.getFuture(completer -> {
             mCaptureCompleter = completer;
+            mWindowOwner = response.getPackageName();
             mBgExecutor.execute(() -> {
                 float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
                         SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
@@ -173,11 +178,13 @@
             if (LogConfig.DEBUG_SCROLL) {
                 Log.d(TAG, "got session " + mSession);
             }
+            mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_STARTED, 0, mWindowOwner);
             requestNextTile(0);
         } catch (InterruptedException | ExecutionException e) {
             // Failure to start, propagate to caller
             Log.e(TAG, "session start failed!");
             mCaptureCompleter.setException(e);
+            mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner);
         }
     }
 
@@ -297,6 +304,11 @@
         if (LogConfig.DEBUG_SCROLL) {
             Log.d(TAG, "finishCapture()");
         }
+        if (mImageTileSet.getHeight() > 0) {
+            mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_COMPLETED, 0, mWindowOwner);
+        } else {
+            mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner);
+        }
         mEndFuture = mSession.end();
         mEndFuture.addListener(() -> {
             if (LogConfig.DEBUG_SCROLL) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 8e52b0d..50911d16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -290,8 +290,8 @@
         default void showAuthenticationDialog(PromptInfo promptInfo,
                 IBiometricSysuiReceiver receiver,
                 int[] sensorIds, boolean credentialAllowed,
-                boolean requireConfirmation, int userId, String opPackageName,
-                long operationId, @BiometricMultiSensorMode int multiSensorConfig) {
+                boolean requireConfirmation, int userId, long operationId, String opPackageName,
+                long requestId, @BiometricMultiSensorMode int multiSensorConfig) {
         }
 
         /** @see IStatusBar#onBiometricAuthenticated() */
@@ -843,7 +843,7 @@
     @Override
     public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
             int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
-            int userId, String opPackageName, long operationId,
+            int userId, long operationId, String opPackageName, long requestId,
             @BiometricMultiSensorMode int multiSensorConfig) {
         synchronized (mLock) {
             SomeArgs args = SomeArgs.obtain();
@@ -855,6 +855,7 @@
             args.argi1 = userId;
             args.arg6 = opPackageName;
             args.arg7 = operationId;
+            args.arg8 = requestId;
             args.argi2 = multiSensorConfig;
             mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args)
                     .sendToTarget();
@@ -1312,8 +1313,9 @@
                                 (boolean) someArgs.arg4 /* credentialAllowed */,
                                 (boolean) someArgs.arg5 /* requireConfirmation */,
                                 someArgs.argi1 /* userId */,
-                                (String) someArgs.arg6 /* opPackageName */,
                                 (long) someArgs.arg7 /* operationId */,
+                                (String) someArgs.arg6 /* opPackageName */,
+                                (long) someArgs.arg8 /* requestId */,
                                 someArgs.argi2 /* multiSensorConfig */);
                     }
                     someArgs.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 503b5c0..0bcec07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -66,6 +66,7 @@
 import com.android.internal.widget.ViewClippingUtil;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.Utils;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
@@ -455,7 +456,8 @@
                     new KeyguardIndication.Builder()
                             .setMessage(mContext.getResources().getString(
                                     com.android.internal.R.string.global_action_logout))
-                            .setTextColor(mInitialTextColorState)
+                            .setTextColor(Utils.getColorAttr(
+                                    mContext, com.android.internal.R.attr.textColorOnAccent))
                             .setBackground(mContext.getDrawable(
                                     com.android.systemui.R.drawable.logout_button_background))
                             .setClickListener((view) -> {
@@ -723,15 +725,13 @@
     }
 
     protected String computePowerIndication() {
-        if (mPowerCharged) {
-            return mContext.getResources().getString(R.string.keyguard_charged);
-        }
-
         int chargingId;
-        String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
         if (mBatteryOverheated) {
             chargingId = R.string.keyguard_plugged_in_charging_limited;
+            String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
             return mContext.getResources().getString(chargingId, percentage);
+        } else if (mPowerCharged) {
+            return mContext.getResources().getString(R.string.keyguard_charged);
         }
 
         final boolean hasChargingTime = mChargingTimeRemaining > 0;
@@ -759,6 +759,7 @@
                     : R.string.keyguard_plugged_in_wireless;
         }
 
+        String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
         if (hasChargingTime) {
             String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes(
                     mContext, mChargingTimeRemaining);
@@ -811,17 +812,12 @@
                 mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState);
             }
         } else if (mKeyguardUpdateMonitor.isScreenOn()) {
-            if (mKeyguardUpdateMonitor.isUdfpsAvailable()) {
-                showTransientIndication(mContext.getString(R.string.keyguard_unlock_press),
-                        false /* isError */, true /* hideOnScreenOff */);
-            } else {
-                showTransientIndication(mContext.getString(R.string.keyguard_unlock),
-                        false /* isError */, true /* hideOnScreenOff */);
-            }
+            showTransientIndication(mContext.getString(R.string.keyguard_unlock),
+                    false /* isError */, true /* hideOnScreenOff */);
         }
     }
 
-    private void showTryFingerprintMsg() {
+    private void showTryFingerprintMsg(String a11yString) {
         if (mKeyguardUpdateMonitor.isUdfpsAvailable()) {
             // if udfps available, there will always be a tappable affordance to unlock
             // For example, the lock icon
@@ -833,6 +829,11 @@
         } else {
             showTransientIndication(R.string.keyguard_try_fingerprint);
         }
+
+        // Although we suppress face auth errors visually, we still announce them for a11y
+        if (!TextUtils.isEmpty(a11yString)) {
+            mLockScreenIndicationView.announceForAccessibility(a11yString);
+        }
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -913,7 +914,7 @@
             } else if (mKeyguardUpdateMonitor.isScreenOn()) {
                 if (biometricSourceType == BiometricSourceType.FACE
                         && shouldSuppressFaceMsgAndShowTryFingerprintMsg()) {
-                    showTryFingerprintMsg();
+                    showTryFingerprintMsg(helpString);
                     return;
                 }
                 showTransientIndication(helpString, false /* isError */, showActionToUnlock);
@@ -933,7 +934,7 @@
                     && shouldSuppressFaceMsgAndShowTryFingerprintMsg()
                     && !mStatusBarKeyguardViewManager.isBouncerShowing()
                     && mKeyguardUpdateMonitor.isScreenOn()) {
-                showTryFingerprintMsg();
+                showTryFingerprintMsg(errString);
                 return;
             }
             if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
@@ -942,7 +943,7 @@
                 if (!mStatusBarKeyguardViewManager.isBouncerShowing()
                         && mKeyguardUpdateMonitor.isUdfpsEnrolled()
                         && mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
-                    showTryFingerprintMsg();
+                    showTryFingerprintMsg(errString);
                 } else if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
                     mStatusBarKeyguardViewManager.showBouncerMessage(
                             mContext.getResources().getString(R.string.keyguard_unlock_press),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 538db61..21ed9da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -149,7 +149,10 @@
  */
 class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
 
-    lateinit var revealAmountListener: Consumer<Float>
+    /**
+     * Listener that is called if the scrim's opaqueness changes
+     */
+    lateinit var isScrimOpaqueChangedListener: Consumer<Boolean>
 
     /**
      * How much of the underlying views are revealed, in percent. 0 means they will be completely
@@ -161,7 +164,7 @@
                 field = value
 
                 revealEffect.setRevealAmountOnScrim(value, this)
-                revealAmountListener.accept(value)
+                updateScrimOpaque()
                 invalidate()
             }
         }
@@ -201,6 +204,31 @@
         }
 
     /**
+     * Is the scrim currently fully opaque
+     */
+    var isScrimOpaque = false
+        private set(value) {
+            if (field != value) {
+                field = value
+                isScrimOpaqueChangedListener.accept(field)
+            }
+        }
+
+    private fun updateScrimOpaque() {
+        isScrimOpaque = revealAmount == 0.0f && alpha == 1.0f && visibility == VISIBLE
+    }
+
+    override fun setAlpha(alpha: Float) {
+        super.setAlpha(alpha)
+        updateScrimOpaque()
+    }
+
+    override fun setVisibility(visibility: Int) {
+        super.setVisibility(visibility)
+        updateScrimOpaque()
+    }
+
+    /**
      * Paint used to draw a transparent-to-white radial gradient. This will be scaled and translated
      * via local matrix in [onDraw] so we never need to construct a new shader.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index b833427..2a8771e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -78,7 +78,8 @@
     private var keyguardAnimator: Animator? = null
     private var notificationAnimator: Animator? = null
     private var updateScheduled: Boolean = false
-    private var shadeExpansion = 0f
+    @VisibleForTesting
+    var shadeExpansion = 0f
     private var isClosed: Boolean = true
     private var isOpen: Boolean = false
     private var isBlurred: Boolean = false
@@ -92,6 +93,9 @@
     // Only for dumpsys
     private var lastAppliedBlur = 0
 
+    // Shade expansion offset that happens when pulling down on a HUN.
+    var panelPullDownMinFraction = 0f
+
     var shadeAnimation = DepthAnimation()
 
     @VisibleForTesting
@@ -181,7 +185,8 @@
                         if (shouldApplyShadeBlur()) shadeExpansion else 0f, false))
         var combinedBlur = (expansionRadius * INTERACTION_BLUR_FRACTION +
                 animationRadius * ANIMATION_BLUR_FRACTION)
-        val qsExpandedRatio = qsPanelExpansion * shadeExpansion
+        val qsExpandedRatio = Interpolators.getNotificationScrimAlpha(qsPanelExpansion,
+                false /* notification */) * shadeExpansion
         combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(qsExpandedRatio))
         combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress))
         var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius)
@@ -311,8 +316,10 @@
     /**
      * Update blurs when pulling down the shade
      */
-    override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) {
+    override fun onPanelExpansionChanged(rawExpansion: Float, tracking: Boolean) {
         val timestamp = SystemClock.elapsedRealtimeNanos()
+        val expansion = MathUtils.saturate(
+                (rawExpansion - panelPullDownMinFraction) / (1f - panelPullDownMinFraction))
 
         if (shadeExpansion == expansion && prevTracking == tracking) {
             prevTimestamp = timestamp
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index f0d779c..6ea79af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -182,10 +182,10 @@
     default void setFaceAuthDisplayBrightness(float brightness) {}
 
     /**
-     * How much {@link LightRevealScrim} obscures the UI.
-     * @param amount 0 when opaque, 1 when not transparent
+     * If {@link LightRevealScrim} obscures the UI.
+     * @param opaque if the scrim is opaque
      */
-    default void setLightRevealScrimAmount(float amount) {}
+    default void setLightRevealScrimOpaque(boolean opaque) {}
 
     /**
      * Custom listener to pipe data back to plugins about whether or not the status bar would be
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
index 4a467ce..d01fc93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
@@ -104,7 +104,7 @@
         // the active effect area. Values here should be kept in sync with the
         // animation implementation in the ripple shader.
         val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
-                (1 - rippleShader.progress)) * radius * 1.5f
+                (1 - rippleShader.progress)) * radius * 2
         canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 71546ae..d432f8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.FeatureFlags
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.util.concurrency.Execution
 import com.android.systemui.util.settings.SecureSettings
 import java.lang.RuntimeException
@@ -67,6 +68,7 @@
     private val contentResolver: ContentResolver,
     private val configurationController: ConfigurationController,
     private val statusBarStateController: StatusBarStateController,
+    private val deviceProvisionedController: DeviceProvisionedController,
     private val execution: Execution,
     @Main private val uiExecutor: Executor,
     @Main private val handler: Handler,
@@ -83,6 +85,55 @@
     private var showSensitiveContentForManagedUser = false
     private var managedUserHandle: UserHandle? = null
 
+    private val deviceProvisionedListener =
+        object : DeviceProvisionedController.DeviceProvisionedListener {
+            override fun onDeviceProvisionedChanged() {
+                connectSession()
+            }
+
+            override fun onUserSetupChanged() {
+                connectSession()
+            }
+        }
+
+    private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
+        execution.assertIsMainThread()
+        val filteredTargets = targets.filter(::filterSmartspaceTarget)
+        plugin?.onTargetsAvailable(filteredTargets)
+    }
+
+    private val userTrackerCallback = object : UserTracker.Callback {
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            execution.assertIsMainThread()
+            reloadSmartspace()
+        }
+    }
+
+    private val settingsObserver = object : ContentObserver(handler) {
+        override fun onChange(selfChange: Boolean, uri: Uri?) {
+            execution.assertIsMainThread()
+            reloadSmartspace()
+        }
+    }
+
+    private val configChangeListener = object : ConfigurationController.ConfigurationListener {
+        override fun onThemeChanged() {
+            execution.assertIsMainThread()
+            updateTextColorFromWallpaper()
+        }
+    }
+
+    private val statusBarStateListener = object : StatusBarStateController.StateListener {
+        override fun onDozeAmountChanged(linear: Float, eased: Float) {
+            execution.assertIsMainThread()
+            smartspaceView.setDozeAmount(eased)
+        }
+    }
+
+    init {
+        deviceProvisionedController.addCallback(deviceProvisionedListener)
+    }
+
     fun isEnabled(): Boolean {
         execution.assertIsMainThread()
 
@@ -144,10 +195,20 @@
         if (plugin == null || session != null) {
             return
         }
-        val session = smartspaceManager.createSmartspaceSession(
-                SmartspaceConfig.Builder(context, "lockscreen").build())
-        session.addOnTargetsAvailableListener(uiExecutor, sessionListener)
 
+        // Only connect after the device is fully provisioned to avoid connection caching
+        // issues
+        if (!deviceProvisionedController.isDeviceProvisioned() ||
+                !deviceProvisionedController.isCurrentUserSetup()) {
+            return
+        }
+
+        val newSession = smartspaceManager.createSmartspaceSession(
+                SmartspaceConfig.Builder(context, "lockscreen").build())
+        newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+        this.session = newSession
+
+        deviceProvisionedController.removeCallback(deviceProvisionedListener)
         userTracker.addCallback(userTrackerCallback, uiExecutor)
         contentResolver.registerContentObserver(
                 secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
@@ -158,8 +219,6 @@
         configurationController.addCallback(configChangeListener)
         statusBarStateController.addCallback(statusBarStateListener)
 
-        this.session = session
-
         reloadSmartspace()
     }
 
@@ -198,43 +257,6 @@
         plugin?.unregisterListener(listener)
     }
 
-    private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
-        execution.assertIsMainThread()
-        val filteredTargets = targets.filter(::filterSmartspaceTarget)
-        plugin?.onTargetsAvailable(filteredTargets)
-    }
-
-    private val userTrackerCallback = object : UserTracker.Callback {
-        override fun onUserChanged(newUser: Int, userContext: Context) {
-            execution.assertIsMainThread()
-            reloadSmartspace()
-        }
-
-        override fun onProfilesChanged(profiles: List<UserInfo>) {
-        }
-    }
-
-    private val settingsObserver = object : ContentObserver(handler) {
-        override fun onChange(selfChange: Boolean, uri: Uri?) {
-            execution.assertIsMainThread()
-            reloadSmartspace()
-        }
-    }
-
-    private val configChangeListener = object : ConfigurationController.ConfigurationListener {
-        override fun onThemeChanged() {
-            execution.assertIsMainThread()
-            updateTextColorFromWallpaper()
-        }
-    }
-
-    private val statusBarStateListener = object : StatusBarStateController.StateListener {
-        override fun onDozeAmountChanged(linear: Float, eased: Float) {
-            execution.assertIsMainThread()
-            smartspaceView.setDozeAmount(eased)
-        }
-    }
-
     private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
         return when (t.userHandle) {
             userTracker.userHandle -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index dba3401..9c755e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -85,23 +85,26 @@
     }
 
     /**
-     * Set the content of this view to be visible in an animated way.
-     *
-     * @param contentVisible True if the content should be visible or false if it should be hidden.
+     * @param visible True if we should animate contents visible
      */
-    public void setContentVisible(boolean contentVisible) {
-        setContentVisible(contentVisible, true /* animate */);
+    public void setContentVisible(boolean visible) {
+        setContentVisible(visible, true /* animate */, null /* runAfter */);
     }
+
     /**
-     * Set the content of this view to be visible.
-     * @param contentVisible True if the content should be visible or false if it should be hidden.
-     * @param animate Should an animation be performed.
+     * @param visible True if the contents should be visible
+     * @param animate True if we should fade to new visibility
+     * @param runAfter Runnable to run after visibility updates
      */
-    private void setContentVisible(boolean contentVisible, boolean animate) {
-        if (mContentVisible != contentVisible) {
+    public void setContentVisible(boolean visible, boolean animate, Runnable runAfter) {
+        if (mContentVisible != visible) {
             mContentAnimating = animate;
-            mContentVisible = contentVisible;
-            setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable);
+            mContentVisible = visible;
+            Runnable endRunnable = runAfter == null ? mContentVisibilityEndRunnable : () -> {
+                mContentVisibilityEndRunnable.run();
+                runAfter.run();
+            };
+            setViewVisible(mContent, visible, animate, endRunnable);
         }
 
         if (!mContentAnimating) {
@@ -113,6 +116,10 @@
         return mContentVisible;
     }
 
+    public void setVisible(boolean nowVisible, boolean animate) {
+        setVisible(nowVisible, animate, null);
+    }
+
     /**
      * Make this view visible. If {@code false} is passed, the view will fade out it's content
      * and set the view Visibility to GONE. If only the content should be changed
@@ -121,7 +128,7 @@
      * @param nowVisible should the view be visible
      * @param animate should the change be animated.
      */
-    public void setVisible(boolean nowVisible, boolean animate) {
+    public void setVisible(boolean nowVisible, boolean animate, Runnable runAfter) {
         if (mIsVisible != nowVisible) {
             mIsVisible = nowVisible;
             if (animate) {
@@ -132,10 +139,10 @@
                 } else {
                     setWillBeGone(true);
                 }
-                setContentVisible(nowVisible, true /* animate */);
+                setContentVisible(nowVisible, true /* animate */, runAfter);
             } else {
                 setVisibility(nowVisible ? VISIBLE : GONE);
-                setContentVisible(nowVisible, false /* animate */);
+                setContentVisible(nowVisible, false /* animate */, runAfter);
                 setWillBeGone(false);
                 notifyHeightChanged(false /* needsAnimation */);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 23aefd9..594afce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -46,6 +46,7 @@
     private Runnable mRoundingChangedCallback;
     private ExpandableNotificationRow mTrackedHeadsUp;
     private float mAppearFraction;
+    private boolean mIsDismissAllInProgress;
 
     private ExpandableView mSwipedView = null;
     private ExpandableView mViewBeforeSwipedView = null;
@@ -162,6 +163,10 @@
         }
     }
 
+    void setDismissAllInProgress(boolean isClearingAll) {
+        mIsDismissAllInProgress = isClearingAll;
+    }
+
     private float getRoundness(ExpandableView view, boolean top) {
         if (view == null) {
             return 0f;
@@ -171,6 +176,11 @@
                 || view == mViewAfterSwipedView) {
             return 1f;
         }
+        if (view instanceof ExpandableNotificationRow
+                && ((ExpandableNotificationRow) view).canViewBeDismissed()
+                && mIsDismissAllInProgress) {
+            return 1.0f;
+        }
         if ((view.isPinned()
                 || (view.isHeadsUpAnimatingAway()) && !mExpanded)) {
             return 1.0f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 0660daa..a771274 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -144,6 +144,10 @@
     private static final boolean DEBUG_REMOVE_ANIMATION = SystemProperties.getBoolean(
             "persist.debug.nssl.dismiss", false /* default */);
 
+    // Delay in milli-seconds before shade closes for clear all.
+    private final int DELAY_BEFORE_SHADE_CLOSE = 200;
+    private boolean mShadeNeedsToClose = false;
+
     private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
     private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
     private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
@@ -259,7 +263,6 @@
     protected FooterView mFooterView;
     protected EmptyShadeView mEmptyShadeView;
     private boolean mDismissAllInProgress;
-    private boolean mFadeNotificationsOnDismiss;
     private FooterDismissListener mFooterDismissListener;
     private boolean mFlingAfterUpEvent;
 
@@ -1192,7 +1195,7 @@
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void clampScrollPosition() {
         int scrollRange = getScrollRange();
-        if (scrollRange < mOwnScrollY) {
+        if (scrollRange < mOwnScrollY && !mAmbientState.isDismissAllInProgress()) {
             boolean animateStackY = false;
             if (scrollRange < getScrollAmountToScrollBoundary()
                     && mAnimateStackYForContentHeightChange) {
@@ -1708,6 +1711,11 @@
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
+        if (child instanceof SectionHeaderView) {
+             ((StackScrollerDecorView) child).setContentVisible(
+                     false /* visible */, true /* animate */, endRunnable);
+             return;
+        }
         mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
                 true /* isDismissAll */);
     }
@@ -4050,6 +4058,19 @@
         runAnimationFinishedRunnables();
         clearTransient();
         clearHeadsUpDisappearRunning();
+
+        if (mAmbientState.isDismissAllInProgress()) {
+            setDismissAllInProgress(false);
+
+            if (mShadeNeedsToClose) {
+                mShadeNeedsToClose = false;
+                postDelayed(
+                        () -> {
+                            mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+                        },
+                        DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
+            }
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -4407,6 +4428,7 @@
     public void setDismissAllInProgress(boolean dismissAllInProgress) {
         mDismissAllInProgress = dismissAllInProgress;
         mAmbientState.setDismissAllInProgress(dismissAllInProgress);
+        mController.getNoticationRoundessManager().setDismissAllInProgress(dismissAllInProgress);
         handleDismissAllClipping();
     }
 
@@ -4947,64 +4969,129 @@
         mHeadsUpAppearanceController = headsUpAppearanceController;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    @VisibleForTesting
-    void clearNotifications(@SelectedRows int selection, boolean closeShade) {
-        // animate-swipe all dismissable notifications, then animate the shade closed
-        int numChildren = getChildCount();
+    private boolean isVisible(View child) {
+        boolean hasClipBounds = child.getClipBounds(mTmpRect);
+        return child.getVisibility() == View.VISIBLE
+                && (!hasClipBounds || mTmpRect.height() > 0);
+    }
 
-        final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
-        final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
-        for (int i = 0; i < numChildren; i++) {
-            final View child = getChildAt(i);
-            if (child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-                boolean parentVisible = false;
-                boolean hasClipBounds = child.getClipBounds(mTmpRect);
-                if (includeChildInDismissAll(row, selection)) {
-                    viewsToRemove.add(row);
-                    if (child.getVisibility() == View.VISIBLE
-                            && (!hasClipBounds || mTmpRect.height() > 0)) {
-                        viewsToHide.add(child);
-                        parentVisible = true;
-                    }
-                } else if (child.getVisibility() == View.VISIBLE
-                        && (!hasClipBounds || mTmpRect.height() > 0)) {
-                    parentVisible = true;
-                }
-                List<ExpandableNotificationRow> children = row.getAttachedChildren();
-                if (children != null) {
-                    for (ExpandableNotificationRow childRow : children) {
-                        if (includeChildInDismissAll(row, selection)) {
-                            viewsToRemove.add(childRow);
-                            if (parentVisible && row.areChildrenExpanded()) {
-                                hasClipBounds = childRow.getClipBounds(mTmpRect);
-                                if (childRow.getVisibility() == View.VISIBLE
-                                        && (!hasClipBounds || mTmpRect.height() > 0)) {
-                                    viewsToHide.add(childRow);
-                                }
-                            }
+    private boolean shouldHideParent(View view, @SelectedRows int selection) {
+        final boolean silentSectionWillBeGone =
+                !mController.hasNotifications(ROWS_GENTLE, false /* clearable */);
+
+        // The only SectionHeaderView we have is the silent section header.
+        if (view instanceof SectionHeaderView && silentSectionWillBeGone) {
+            return true;
+        }
+        if (view instanceof ExpandableNotificationRow) {
+            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+            if (isVisible(row) && includeChildInDismissAll(row, selection)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isChildrenVisible(ExpandableNotificationRow parent) {
+        List<ExpandableNotificationRow> children = parent.getAttachedChildren();
+        return isVisible(parent)
+                && children != null
+                && parent.areChildrenExpanded();
+    }
+
+    // Similar to #getRowsToDismissInBackend, but filters for visible views.
+    private ArrayList<View> getVisibleViewsToAnimateAway(@SelectedRows int selection) {
+        final int viewCount = getChildCount();
+        final ArrayList<View> viewsToHide = new ArrayList<>(viewCount);
+
+        for (int i = 0; i < viewCount; i++) {
+            final View view = getChildAt(i);
+
+            if (shouldHideParent(view, selection)) {
+                viewsToHide.add(view);
+            }
+            if (view instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+
+                if (isChildrenVisible(parent)) {
+                    for (ExpandableNotificationRow child : parent.getAttachedChildren()) {
+                        if (isVisible(child) && includeChildInDismissAll(child, selection)) {
+                            viewsToHide.add(child);
                         }
                     }
                 }
             }
         }
+        return viewsToHide;
+    }
 
+    private ArrayList<ExpandableNotificationRow> getRowsToDismissInBackend(
+            @SelectedRows int selection) {
+        final int childCount = getChildCount();
+        final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(childCount);
+
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            if (!(view instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+            if (includeChildInDismissAll(parent, selection)) {
+                viewsToRemove.add(parent);
+            }
+            List<ExpandableNotificationRow> children = parent.getAttachedChildren();
+            if (isVisible(parent) && children != null) {
+                for (ExpandableNotificationRow child : children) {
+                    if (includeChildInDismissAll(parent, selection)) {
+                        viewsToRemove.add(child);
+                    }
+                }
+            }
+        }
+        return viewsToRemove;
+    }
+
+    /**
+     * Collects a list of visible rows, and animates them away in a staggered fashion as if they
+     * were dismissed. Notifications are dismissed in the backend via onDismissAllAnimationsEnd.
+     */
+    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
+    @VisibleForTesting
+    void clearNotifications(@SelectedRows int selection, boolean closeShade) {
+        // Animate-swipe all dismissable notifications, then animate the shade closed
+        final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection);
+        final ArrayList<ExpandableNotificationRow> rowsToDismissInBackend =
+                getRowsToDismissInBackend(selection);
         if (mDismissListener != null) {
             mDismissListener.onDismiss(selection);
         }
-
-        if (viewsToRemove.isEmpty()) {
-            if (closeShade && mShadeController != null) {
-                mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
-            }
+        final Runnable dismissInBackend = () -> {
+            onDismissAllAnimationsEnd(rowsToDismissInBackend, selection);
+        };
+        if (viewsToAnimateAway.isEmpty()) {
+            dismissInBackend.run();
             return;
         }
+        // Disable normal animations
+        setDismissAllInProgress(true);
+        mShadeNeedsToClose = closeShade;
 
-        performDismissAllAnimations(
-                viewsToHide,
-                closeShade,
-                () -> onDismissAllAnimationsEnd(viewsToRemove, selection));
+        // Decrease the delay for every row we animate to give the sense of
+        // accelerating the swipes
+        final int rowDelayDecrement = 5;
+        int currentDelay = 60;
+        int totalDelay = 0;
+        final int numItems = viewsToAnimateAway.size();
+        for (int i = numItems - 1; i >= 0; i--) {
+            View view = viewsToAnimateAway.get(i);
+            Runnable endRunnable = null;
+            if (i == 0) {
+                endRunnable = dismissInBackend;
+            }
+            dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
+            currentDelay = Math.max(30, currentDelay - rowDelayDecrement);
+            totalDelay += currentDelay;
+        }
     }
 
     private boolean includeChildInDismissAll(
@@ -5013,63 +5100,6 @@
         return canChildBeDismissed(row) && matchesSelection(row, selection);
     }
 
-    /**
-     * Given a list of rows, animates them away in a staggered fashion as if they were dismissed.
-     * Doesn't actually dismiss them, though -- that must be done in the onAnimationComplete
-     * handler.
-     *
-     * @param hideAnimatedList List of rows to animated away. Should only be views that are
-     *                         currently visible, or else the stagger will look funky.
-     * @param closeShade Whether to close the shade after the stagger animation completes.
-     * @param onAnimationComplete Called after the entire animation completes (including the shade
-     *                            closing if appropriate). The rows must be dismissed for real here.
-     */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    private void performDismissAllAnimations(
-            final ArrayList<View> hideAnimatedList,
-            final boolean closeShade,
-            final Runnable onAnimationComplete) {
-
-        final Runnable onSlideAwayAnimationComplete = () -> {
-            if (closeShade) {
-                mShadeController.addPostCollapseAction(() -> {
-                    setDismissAllInProgress(false);
-                    onAnimationComplete.run();
-                });
-                mShadeController.animateCollapsePanels(
-                        CommandQueue.FLAG_EXCLUDE_NONE);
-            } else {
-                setDismissAllInProgress(false);
-                onAnimationComplete.run();
-            }
-        };
-
-        if (hideAnimatedList.isEmpty()) {
-            onSlideAwayAnimationComplete.run();
-            return;
-        }
-
-        // let's disable our normal animations
-        setDismissAllInProgress(true);
-
-        // Decrease the delay for every row we animate to give the sense of
-        // accelerating the swipes
-        int rowDelayDecrement = 10;
-        int currentDelay = 140;
-        int totalDelay = 180;
-        int numItems = hideAnimatedList.size();
-        for (int i = numItems - 1; i >= 0; i--) {
-            View view = hideAnimatedList.get(i);
-            Runnable endRunnable = null;
-            if (i == 0) {
-                endRunnable = onSlideAwayAnimationComplete;
-            }
-            dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
-            currentDelay = Math.max(50, currentDelay - rowDelayDecrement);
-            totalDelay += currentDelay;
-        }
-    }
-
     public void setNotificationActivityStarter(
             NotificationActivityStarter notificationActivityStarter) {
         mNotificationActivityStarter = notificationActivityStarter;
@@ -5085,6 +5115,7 @@
                 mFooterDismissListener.onDismiss();
             }
             clearNotifications(ROWS_ALL, true /* closeShade */);
+            footerView.setSecondaryVisible(false /* visible */, true /* animate */);
         });
         footerView.setManageButtonClickListener(v -> {
             mNotificationActivityStarter.startHistoryIntent(v, mFooterView.isHistoryShown());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 9e4adce..faba48f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1122,6 +1122,10 @@
                 mZenModeController.areNotificationsHiddenInShade());
     }
 
+    public boolean areNotificationsHiddenInShade() {
+        return mZenModeController.areNotificationsHiddenInShade();
+    }
+
     public boolean isShowingEmptyShadeView() {
         return mShowEmptyShadeView;
     }
@@ -1170,6 +1174,10 @@
      * Return whether there are any clearable notifications
      */
     public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
+        return hasNotifications(selection, true /* clearable */);
+    }
+
+    public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
         if (mDynamicPrivacyController.isInLockedDownShade()) {
             return false;
         }
@@ -1180,8 +1188,11 @@
                 continue;
             }
             final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            if (row.canViewBeDismissed() &&
-                    NotificationStackScrollLayout.matchesSelection(row, selection)) {
+            final boolean matchClearable =
+                    isClearable ? row.canViewBeDismissed() : !row.canViewBeDismissed();
+            final boolean inSection =
+                    NotificationStackScrollLayout.matchesSelection(row, selection);
+            if (matchClearable && inSection) {
                 return true;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 2c810c9..8be5de7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -366,6 +366,20 @@
         return stackHeight / stackEndHeight;
     }
 
+    public boolean hasOngoingNotifs(StackScrollAlgorithmState algorithmState) {
+        for (int i = 0; i < algorithmState.visibleChildren.size(); i++) {
+            View child = algorithmState.visibleChildren.get(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            if (!row.canViewBeDismissed()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     // TODO(b/172289889) polish shade open from HUN
     /**
      * Populates the {@link ExpandableViewState} for a single child.
@@ -430,7 +444,9 @@
                         + view.getIntrinsicHeight();
                 final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
                 ((FooterView.FooterViewState) viewState).hideContent =
-                        isShelfShowing || noSpaceForFooter;
+                        isShelfShowing || noSpaceForFooter
+                                || (ambientState.isDismissAllInProgress()
+                                && !hasOngoingNotifs(algorithmState));
             }
         } else {
             if (view != ambientState.getTrackedHeadsUpRow()) {
@@ -467,7 +483,6 @@
                     }
                 }
             }
-
             // Clip height of view right before shelf.
             viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index ee12b4b..4466cfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -46,7 +46,7 @@
     public static final int ANIMATION_DURATION_WAKEUP = 500;
     public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
     public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
-    public static final int ANIMATION_DURATION_SWIPE = 260;
+    public static final int ANIMATION_DURATION_SWIPE = 200;
     public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
     public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
     public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 5a6db21..e67c6ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -291,6 +291,7 @@
 
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.print("getAlwaysOn(): "); pw.println(getAlwaysOn());
         pw.print("getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
         pw.print("getPulseDuration(): "); pw.println(getPulseDuration());
         pw.print("getPulseInDuration(): "); pw.println(getPulseInDuration());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index b2cf72a..21c3e5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -116,11 +116,18 @@
 
         if (!mDozing || mPulseCallback != null) {
             if (DEBUG) {
-                Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? "
+                Log.d(TAG, "Pulse suppressed. Dozing: " + mDozeParameters + " had callback? "
                         + (mPulseCallback != null));
             }
             // Pulse suppressed.
             callback.onPulseFinished();
+            if (!mDozing) {
+                mDozeLog.tracePulseDropped("device isn't dozing");
+            } else {
+                mDozeLog.tracePulseDropped("already has pulse callback mPulseCallback="
+                        + mPulseCallback);
+            }
+
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 0a4e59c..4701d8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -203,13 +203,15 @@
     private ControlsListingController.ControlsListingCallback mListingCallback =
             new ControlsListingController.ControlsListingCallback() {
                 public void onServicesUpdated(List<ControlsServiceInfo> serviceInfos) {
-                    boolean available = !serviceInfos.isEmpty();
+                    post(() -> {
+                        boolean available = !serviceInfos.isEmpty();
 
-                    if (available != mControlServicesAvailable) {
-                        mControlServicesAvailable = available;
-                        updateControlsVisibility();
-                        updateAffordanceColors();
-                    }
+                        if (available != mControlServicesAvailable) {
+                            mControlServicesAvailable = available;
+                            updateControlsVisibility();
+                            updateAffordanceColors();
+                        }
+                    });
                 }
             };
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index f1cde8a..a5b5f1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -28,7 +28,10 @@
 import android.view.View;
 import android.widget.TextView;
 
+import androidx.annotation.StyleRes;
+
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.keyguard.KeyguardIndication;
 
@@ -39,6 +42,12 @@
  */
 public class KeyguardIndicationTextView extends TextView {
     private static final long MSG_MIN_DURATION_MILLIS_DEFAULT = 1500;
+
+    @StyleRes
+    private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea;
+    @StyleRes
+    private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button;
+
     private long mNextAnimationTime = 0;
     private boolean mAnimationsEnabled = true;
     private LinkedList<CharSequence> mMessages = new LinkedList<>();
@@ -136,6 +145,14 @@
             public void onAnimationEnd(Animator animator) {
                 KeyguardIndication info = mKeyguardIndicationInfo.poll();
                 if (info != null) {
+                    // First, update the style.
+                    // If a background is set on the text, we don't want shadow on the text
+                    if (info.getBackground() != null) {
+                        setTextAppearance(sButtonStyleId);
+                    } else {
+                        setTextAppearance(sStyleId);
+                    }
+                    setBackground(info.getBackground());
                     setTextColor(info.getTextColor());
                     setOnClickListener(info.getClickListener());
                     setClickable(info.getClickListener() != null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index e272d27..55e0c9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -96,7 +96,8 @@
     private final UserManager mUserManager;
 
     private int mSystemIconsSwitcherHiddenExpandedMargin;
-    private int mSystemIconsBaseMargin;
+    private int mStatusBarPaddingEnd;
+    private int mMinDotWidth;
     private View mSystemIconsContainer;
     private TintedIconManager mIconManager;
     private List<String> mBlockedIcons = new ArrayList<>();
@@ -157,14 +158,7 @@
         mMultiUserAvatar.setLayoutParams(lp);
 
         // System icons
-        lp = (MarginLayoutParams) mSystemIconsContainer.getLayoutParams();
-        lp.setMarginStart(getResources().getDimensionPixelSize(
-                R.dimen.system_icons_super_container_margin_start));
-        mSystemIconsContainer.setLayoutParams(lp);
-        mSystemIconsContainer.setPaddingRelative(mSystemIconsContainer.getPaddingStart(),
-                mSystemIconsContainer.getPaddingTop(),
-                getResources().getDimensionPixelSize(R.dimen.system_icons_keyguard_padding_end),
-                mSystemIconsContainer.getPaddingBottom());
+        updateSystemIconsLayoutParams();
 
         // Respect font size setting.
         mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
@@ -194,8 +188,10 @@
         Resources res = getResources();
         mSystemIconsSwitcherHiddenExpandedMargin = res.getDimensionPixelSize(
                 R.dimen.system_icons_switcher_hidden_expanded_margin);
-        mSystemIconsBaseMargin = res.getDimensionPixelSize(
-                R.dimen.system_icons_super_container_avatarless_margin_end);
+        mStatusBarPaddingEnd = res.getDimensionPixelSize(
+                R.dimen.status_bar_padding_end);
+        mMinDotWidth = res.getDimensionPixelSize(
+                R.dimen.ongoing_appops_dot_min_padding);
         mCutoutSideNudge = getResources().getDimensionPixelSize(
                 R.dimen.display_cutout_margin_consumption);
         mShowPercentAvailable = getContext().getResources().getBoolean(
@@ -243,16 +239,24 @@
     private void updateSystemIconsLayoutParams() {
         LinearLayout.LayoutParams lp =
                 (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
-        // If the avatar icon is gone, we need to have some end margin to display the system icons
-        // correctly.
-        int baseMarginEnd = mMultiUserAvatar.getVisibility() == View.GONE
-                ? mSystemIconsBaseMargin
-                : 0;
+
+        int marginStart = getResources().getDimensionPixelSize(
+                R.dimen.system_icons_super_container_margin_start);
+
+        // Use status_bar_padding_end to replace original
+        // system_icons_super_container_avatarless_margin_end to prevent different end alignment
+        // between PhoneStatusBarView and KeyguardStatusBarView
+        int baseMarginEnd = mStatusBarPaddingEnd;
         int marginEnd =
                 mKeyguardUserSwitcherEnabled ? mSystemIconsSwitcherHiddenExpandedMargin
                         : baseMarginEnd;
-        marginEnd = calculateMargin(marginEnd, mPadding.second);
-        if (marginEnd != lp.getMarginEnd()) {
+
+        // Align PhoneStatusBar right margin/padding, only use
+        // 1. status bar layout: mPadding(consider round_corner + privacy dot)
+        // 2. icon container: R.dimen.status_bar_padding_end
+
+        if (marginEnd != lp.getMarginEnd() || marginStart != lp.getMarginStart()) {
+            lp.setMarginStart(marginStart);
             lp.setMarginEnd(marginEnd);
             mSystemIconsContainer.setLayoutParams(lp);
         }
@@ -287,7 +291,13 @@
         mPadding =
                 StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
                         mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding);
-        setPadding(mPadding.first, waterfallTop, mPadding.second, 0);
+
+        // consider privacy dot space
+        final int minLeft = isLayoutRtl() ? Math.max(mMinDotWidth, mPadding.first) : mPadding.first;
+        final int minRight = isLayoutRtl() ? mPadding.second :
+                Math.max(mMinDotWidth, mPadding.second);
+
+        setPadding(minLeft, waterfallTop, minRight, 0);
     }
 
     private boolean updateLayoutParamsNoCutout() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 15e0716..e6bb3062 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -635,6 +635,7 @@
     private int mQsClipBottom;
     private boolean mQsVisible;
     private final ContentResolver mContentResolver;
+    private float mMinFraction;
 
     private final Executor mUiExecutor;
     private final SecureSettings mSecureSettings;
@@ -1795,6 +1796,15 @@
         return !mQsTouchAboveFalsingThreshold;
     }
 
+    /**
+     * Percentage of panel expansion offset, caused by pulling down on a heads-up.
+     */
+    @Override
+    public void setMinFraction(float minFraction) {
+        mMinFraction = minFraction;
+        mDepthController.setPanelPullDownMinFraction(mMinFraction);
+    }
+
     private float computeQsExpansionFraction() {
         if (mQSAnimatingHiddenFromCollapsed) {
             // When hiding QS from collapsed state, the expansion can sometimes temporarily
@@ -2309,6 +2319,12 @@
                 }
             }
             top += mOverStretchAmount;
+            // Correction for instant expansion caused by HUN pull down/
+            if (mMinFraction > 0f && mMinFraction < 1f) {
+                float realFraction =
+                        (getExpandedFraction() - mMinFraction) / (1f - mMinFraction);
+                top *= MathUtils.saturate(realFraction / mMinFraction);
+            }
             bottom = getView().getBottom();
             // notification bounds should take full screen width regardless of insets
             left = 0;
@@ -3439,7 +3455,7 @@
     }
 
     public void setPanelScrimMinFraction(float minFraction) {
-        mBar.panelScrimMinFractionChanged(minFraction);
+        mBar.onPanelMinFractionChanged(minFraction);
     }
 
     public void clearNotificationEffects() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 246810a..c26782b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -605,12 +605,11 @@
     }
 
     @Override
-    public void setLightRevealScrimAmount(float amount) {
-        boolean lightRevealScrimOpaque = amount == 0;
-        if (mCurrentState.mLightRevealScrimOpaque == lightRevealScrimOpaque) {
+    public void setLightRevealScrimOpaque(boolean opaque) {
+        if (mCurrentState.mLightRevealScrimOpaque == opaque) {
             return;
         }
-        mCurrentState.mLightRevealScrimOpaque = lightRevealScrimOpaque;
+        mCurrentState.mLightRevealScrimOpaque = opaque;
         apply(mCurrentState);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index b5d9bd6..e57e200 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -311,6 +311,12 @@
                     // Capture all touch events in always-on.
                     return true;
                 }
+
+                if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+                    // capture all touches if the alt auth bouncer is showing
+                    return true;
+                }
+
                 boolean intercept = false;
                 if (mNotificationPanelViewController.isFullyExpanded()
                         && mDragDownHelper.isDragDownEnabled()
@@ -338,6 +344,12 @@
                 if (mStatusBarStateController.isDozing()) {
                     handled = !mService.isPulsing();
                 }
+
+                if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+                    // eat the touch
+                    handled = true;
+                }
+
                 if ((mDragDownHelper.isDragDownEnabled() && !handled)
                         || mDragDownHelper.isDraggingDown()) {
                     // we still want to finish our drag down gesture when locking the screen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index f1b6c7c..eca91a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -18,6 +18,7 @@
 
 import static java.lang.Float.isNaN;
 
+import android.annotation.CallSuper;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Parcelable;
@@ -162,7 +163,13 @@
         return mPanel == null || mPanel.getView().dispatchTouchEvent(event);
     }
 
-    public abstract void panelScrimMinFractionChanged(float minFraction);
+    /**
+     * Percentage of panel expansion offset, caused by pulling down on a heads-up.
+     */
+    @CallSuper
+    public void onPanelMinFractionChanged(float minFraction) {
+        mPanel.setMinFraction(minFraction);
+    }
 
     /**
      * @param frac the fraction from the expansion in [0, 1]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 323a112..99c0c13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -338,6 +338,13 @@
     protected abstract float getOpeningHeight();
 
     /**
+     * Minimum fraction from where expansion should start. This is set when pulling down on a
+     * heads-up notification.
+     * @param minFraction Fraction from 0 to 1.
+     */
+    public abstract void setMinFraction(float minFraction);
+
+    /**
      * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
      * horizontal direction
      */
@@ -1211,10 +1218,14 @@
                 case MotionEvent.ACTION_MOVE:
                     final float h = y - mInitialTouchY;
                     addMovement(event);
-                    if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown) {
+                    final boolean openShadeWithoutHun =
+                            mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown;
+                    if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown
+                            || openShadeWithoutHun) {
                         float hAbs = Math.abs(h);
                         float touchSlop = getTouchSlop(event);
-                        if ((h < -touchSlop || (mAnimatingOnDown && hAbs > touchSlop))
+                        if ((h < -touchSlop
+                                || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop))
                                 && hAbs > Math.abs(x - mInitialTouchX)) {
                             cancelHeightAnimator();
                             startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
@@ -1227,10 +1238,7 @@
                     mVelocityTracker.clear();
                     break;
             }
-
-            // Finally, if none of the above cases applies, ensure that touches do not get handled
-            // by the contents of a panel that is not showing (a bit of a hack to avoid b/178277858)
-            return (mView.getVisibility() != View.VISIBLE);
+            return false;
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index c300b11..2ca36b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -269,10 +269,11 @@
     }
 
     @Override
-    public void panelScrimMinFractionChanged(float minFraction) {
+    public void onPanelMinFractionChanged(float minFraction) {
         if (isNaN(minFraction)) {
             throw new IllegalArgumentException("minFraction cannot be NaN");
         }
+        super.onPanelMinFractionChanged(minFraction);
         if (mMinFraction != minFraction) {
             mMinFraction = minFraction;
             updateScrimFraction();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 7d25aee..43ec6e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -587,6 +587,8 @@
         if (isNaN(expansionFraction)) {
             return;
         }
+        expansionFraction = Interpolators
+                .getNotificationScrimAlpha(expansionFraction, false /* notification */);
         boolean qsBottomVisible = qsPanelBottomY > 0;
         if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) {
             mQsExpansion = expansionFraction;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 2c0de62..15b8c67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -279,15 +279,41 @@
     BUBBLE_EXPANDED {
         @Override
         public void prepare(ScrimState previousState) {
-            mFrontTint = Color.TRANSPARENT;
-            mBehindTint = Color.TRANSPARENT;
-            mBubbleTint = Color.BLACK;
+            mBehindAlpha = mClipQsScrim ? 1 : 0;
+            mNotifAlpha = 0;
+            mFrontAlpha = 0;
 
-            mFrontAlpha = 0f;
-            mBehindAlpha = mDefaultScrimAlpha;
+            mAnimationDuration = mKeyguardFadingAway
+                    ? mKeyguardFadingAwayDuration
+                    : StatusBar.FADE_KEYGUARD_DURATION;
+
+            mAnimateChange = !mLaunchingAffordanceWithPreview;
+
+            mFrontTint = Color.TRANSPARENT;
+            mBehindTint = Color.BLACK;
+            mBubbleTint = Color.BLACK;
+            mBlankScreen = false;
+
+            if (previousState == ScrimState.AOD) {
+                // Set all scrims black, before they fade transparent.
+                updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
+                updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
+                if (mScrimForBubble != null) {
+                    updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
+                }
+
+                // Scrims should still be black at the end of the transition.
+                mFrontTint = Color.BLACK;
+                mBehindTint = Color.BLACK;
+                mBubbleTint = Color.BLACK;
+                mBlankScreen = true;
+            }
+
+            if (mClipQsScrim) {
+                updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+            }
 
             mAnimationDuration = ScrimController.ANIMATION_DURATION;
-            mBlankScreen = false;
         }
     };
 
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 4705a36..d7a359e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1044,7 +1044,7 @@
                 mNotificationShadeWindowViewController,
                 mNotificationPanelViewController,
                 mAmbientIndicationContainer);
-        mDozeParameters.addCallback(this::updateLightRevealScrimVisibility);
+        updateLightRevealScrimVisibility();
 
         mConfigurationController.addCallback(this);
 
@@ -1261,8 +1261,19 @@
         mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront, scrimForBubble);
 
         mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
-        mLightRevealScrim.setRevealAmountListener(
-                mNotificationShadeWindowController::setLightRevealScrimAmount);
+        mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> {
+            Runnable updateOpaqueness = () -> {
+                mNotificationShadeWindowController.setLightRevealScrimOpaque(
+                        mLightRevealScrim.isScrimOpaque());
+            };
+            if (opaque) {
+                // Delay making the view opaque for a frame, because it needs some time to render
+                // otherwise this can lead to a flicker where the scrim doesn't cover the screen
+                mLightRevealScrim.post(updateOpaqueness);
+            } else {
+                updateOpaqueness.run();
+            }
+        });
         mUnlockedScreenOffAnimationController.initialize(this, mLightRevealScrim);
         updateLightRevealScrimVisibility();
 
@@ -4956,11 +4967,5 @@
         }
 
         mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
-        if (mFeatureFlags.useNewLockscreenAnimations()
-                && (mDozeParameters.getAlwaysOn() || mDozeParameters.isQuickPickupEnabled())) {
-            mLightRevealScrim.setVisibility(View.VISIBLE);
-        } else {
-            mLightRevealScrim.setVisibility(View.GONE);
-        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8a7708a..720690f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -194,6 +194,7 @@
     private boolean mLastGesturalNav;
     private boolean mLastIsDocked;
     private boolean mLastPulsing;
+    private boolean mLastAnimatedToSleep;
     private int mLastBiometricMode;
     private boolean mQsExpanded;
     private boolean mAnimatedToSleep;
@@ -303,8 +304,10 @@
      * Sets a new alt auth interceptor.
      */
     public void setAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) {
-        mAlternateAuthInterceptor = authInterceptor;
-        resetAlternateAuth(false);
+        if (!Objects.equals(mAlternateAuthInterceptor, authInterceptor)) {
+            mAlternateAuthInterceptor = authInterceptor;
+            resetAlternateAuth(false);
+        }
     }
 
     private void registerListeners() {
@@ -990,6 +993,7 @@
         mLastBiometricMode = mBiometricUnlockController.getMode();
         mLastGesturalNav = mGesturalNav;
         mLastIsDocked = mIsDocked;
+        mLastAnimatedToSleep = mAnimatedToSleep;
         mStatusBar.onKeyguardViewManagerStatesUpdated();
     }
 
@@ -1033,7 +1037,7 @@
         boolean hideWhileDozing = mLastDozing && mLastBiometricMode != MODE_WAKE_AND_UNLOCK_PULSING;
         boolean keyguardWithGestureNav = (keyguardShowing && !mLastDozing
                 || mLastPulsing && !mLastIsDocked) && mLastGesturalNav;
-        return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
+        return (!mLastAnimatedToSleep && !keyguardShowing && !hideWhileDozing || mLastBouncerShowing
                 || mLastRemoteInputActive || keyguardWithGestureNav
                 || mLastGlobalActionsVisible);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 47deb1f..8821de0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -310,17 +310,11 @@
     private void onNotificationRemoved(String key, StatusBarNotification old, int reason) {
         if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
 
-        if (old != null) {
-            if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
-                    && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
-                if (mStatusBarStateController.getState() == StatusBarState.SHADE
-                        && reason != NotificationListenerService.REASON_CLICK) {
-                    mCommandQueue.animateCollapsePanels();
-                } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
+        if (old != null && CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
+                && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()
+                && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
                         && !isCollapsing()) {
-                    mStatusBarStateController.setState(StatusBarState.KEYGUARD);
-                }
-            }
+                mStatusBarStateController.setState(StatusBarState.KEYGUARD);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 6b52dca..f8120a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -5,20 +5,23 @@
 import android.animation.ValueAnimator
 import android.content.Context
 import android.content.res.Configuration
+import android.database.ContentObserver
 import android.os.Handler
+import android.provider.Settings
+import com.android.systemui.statusbar.StatusBarState
 import android.view.View
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.statusbar.LightRevealScrim
-import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.StatusBarStateControllerImpl
 import com.android.systemui.statusbar.notification.AnimatableProperty
 import com.android.systemui.statusbar.notification.PropertyAnimator
 import com.android.systemui.statusbar.notification.stack.AnimationProperties
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 
 /**
@@ -46,13 +49,16 @@
     private val statusBarStateControllerImpl: StatusBarStateControllerImpl,
     private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>,
     private val keyguardStateController: KeyguardStateController,
-    private val dozeParameters: dagger.Lazy<DozeParameters>
+    private val dozeParameters: dagger.Lazy<DozeParameters>,
+    private val globalSettings: GlobalSettings
 ) : WakefulnessLifecycle.Observer {
     private val handler = Handler()
 
     private lateinit var statusBar: StatusBar
     private lateinit var lightRevealScrim: LightRevealScrim
 
+    private var animatorDurationScale = 1f
+    private var shouldAnimateInKeyguard = false
     private var lightRevealAnimationPlaying = false
     private var aodUiAnimationPlaying = false
 
@@ -79,6 +85,12 @@
         })
     }
 
+    val animatorDurationScaleObserver = object : ContentObserver(null) {
+        override fun onChange(selfChange: Boolean) {
+            updateAnimatorDurationScale()
+        }
+    }
+
     fun initialize(
         statusBar: StatusBar,
         lightRevealScrim: LightRevealScrim
@@ -86,14 +98,25 @@
         this.lightRevealScrim = lightRevealScrim
         this.statusBar = statusBar
 
+        updateAnimatorDurationScale()
+        globalSettings.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+                /* notify for descendants */ false,
+                animatorDurationScaleObserver)
         wakefulnessLifecycle.addObserver(this)
     }
 
+    fun updateAnimatorDurationScale() {
+        animatorDurationScale =
+                globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
+    }
+
     /**
      * Animates in the provided keyguard view, ending in the same position that it will be in on
      * AOD.
      */
     fun animateInKeyguard(keyguardView: View, after: Runnable) {
+        shouldAnimateInKeyguard = false
         keyguardView.alpha = 0f
         keyguardView.visibility = View.VISIBLE
 
@@ -138,6 +161,7 @@
         // Waking up, so reset this flag.
         decidedToAnimateGoingToSleep = null
 
+        shouldAnimateInKeyguard = false
         lightRevealAnimator.cancel()
         handler.removeCallbacksAndMessages(null)
     }
@@ -146,7 +170,6 @@
         // Set this to false in onFinishedWakingUp rather than onStartedWakingUp so that other
         // observers (such as StatusBar) can ask us whether we were playing the screen off animation
         // and reset accordingly.
-        lightRevealAnimationPlaying = false
         aodUiAnimationPlaying = false
 
         // If we can't control the screen off animation, we shouldn't mess with the StatusBar's
@@ -167,15 +190,15 @@
         if (dozeParameters.get().shouldControlUnlockedScreenOff()) {
             decidedToAnimateGoingToSleep = true
 
+            shouldAnimateInKeyguard = true
             lightRevealAnimationPlaying = true
             lightRevealAnimator.start()
-
             handler.postDelayed({
                 aodUiAnimationPlaying = true
 
                 // Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard.
                 statusBar.notificationPanelViewController.showAodUi()
-            }, ANIMATE_IN_KEYGUARD_DELAY)
+            }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong())
         } else {
             decidedToAnimateGoingToSleep = false
         }
@@ -228,6 +251,10 @@
         return lightRevealAnimationPlaying || aodUiAnimationPlaying
     }
 
+    fun shouldAnimateInKeyguard(): Boolean {
+        return shouldAnimateInKeyguard
+    }
+
     /**
      * Whether the light reveal animation is playing. The second part of the screen off animation,
      * where AOD animates in, might still be playing if this returns false.
@@ -235,4 +262,4 @@
     fun isScreenOffLightRevealAnimationPlaying(): Boolean {
         return lightRevealAnimationPlaying
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index ab58286..6d6320e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -40,6 +40,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.settings.UserTracker;
+import com.android.wifitrackerlib.MergedCarrierEntry;
 import com.android.wifitrackerlib.WifiEntry;
 import com.android.wifitrackerlib.WifiPickerTracker;
 
@@ -68,6 +69,7 @@
 
     private final ArrayList<AccessPointCallback> mCallbacks = new ArrayList<AccessPointCallback>();
     private final UserManager mUserManager;
+    private final UserTracker mUserTracker;
     private final Executor mMainExecutor;
 
     private @Nullable WifiPickerTracker mWifiPickerTracker;
@@ -84,6 +86,7 @@
             WifiPickerTrackerFactory wifiPickerTrackerFactory
     ) {
         mUserManager = userManager;
+        mUserTracker = userTracker;
         mCurrentUser = userTracker.getUserId();
         mMainExecutor = mainExecutor;
         mWifiPickerTrackerFactory = wifiPickerTrackerFactory;
@@ -118,6 +121,11 @@
                 new UserHandle(mCurrentUser));
     }
 
+    public boolean canConfigMobileData() {
+        return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+                UserHandle.of(mCurrentUser)) && mUserTracker.getUserInfo().isAdmin();
+    }
+
     public void onUserSwitched(int newUserId) {
         mCurrentUser = newUserId;
     }
@@ -157,6 +165,15 @@
     }
 
     @Override
+    public MergedCarrierEntry getMergedCarrierEntry() {
+        if (mWifiPickerTracker == null) {
+            fireAcccessPointsCallback(Collections.emptyList());
+            return null;
+        }
+        return mWifiPickerTracker.getMergedCarrierEntry();
+    }
+
+    @Override
     public int getIcon(WifiEntry ap) {
         int level = ap.getLevel();
         return ICONS[Math.max(0, level)];
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index ef2ca98..eeea699 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -23,6 +23,7 @@
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.wifitrackerlib.MergedCarrierEntry;
 import com.android.wifitrackerlib.WifiEntry;
 
 import java.util.List;
@@ -223,9 +224,11 @@
         void addAccessPointCallback(AccessPointCallback callback);
         void removeAccessPointCallback(AccessPointCallback callback);
         void scanForAccessPoints();
+        MergedCarrierEntry getMergedCarrierEntry();
         int getIcon(WifiEntry ap);
         boolean connect(WifiEntry ap);
         boolean canConfigWifi();
+        boolean canConfigMobileData();
 
         public interface AccessPointCallback {
             void onAccessPointsChanged(List<WifiEntry> accessPoints);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index c49de7a..af0d413 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -68,9 +68,12 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
+import com.android.systemui.qs.tiles.dialog.InternetDialogUtil;
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -191,6 +194,8 @@
     private boolean mUserSetup;
     private boolean mSimDetected;
     private boolean mForceCellularValidated;
+    private InternetDialogFactory mInternetDialogFactory;
+    private Handler mMainHandler;
 
     private ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
@@ -221,7 +226,9 @@
             DemoModeController demoModeController,
             CarrierConfigTracker carrierConfigTracker,
             FeatureFlags featureFlags,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            @Main Handler handler,
+            InternetDialogFactory internetDialogFactory) {
         this(context, connectivityManager,
                 telephonyManager,
                 telephonyListenerManager,
@@ -242,6 +249,8 @@
                 featureFlags,
                 dumpManager);
         mReceiverHandler.post(mRegisterListeners);
+        mMainHandler = handler;
+        mInternetDialogFactory = internetDialogFactory;
     }
 
     @VisibleForTesting
@@ -480,6 +489,9 @@
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        if (InternetDialogUtil.isProviderModelEnabled(mContext)) {
+            filter.addAction(Settings.Panel.ACTION_INTERNET_CONNECTIVITY);
+        }
         mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
         mListening = true;
 
@@ -788,6 +800,10 @@
                 mConfig = Config.readConfig(mContext);
                 mReceiverHandler.post(this::handleConfigurationChanged);
                 break;
+            case Settings.Panel.ACTION_INTERNET_CONNECTIVITY:
+                mMainHandler.post(() -> mInternetDialogFactory.create(true,
+                        mAccessPoints.canConfigMobileData(), mAccessPoints.canConfigWifi()));
+                break;
             default:
                 int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index 22fd93e..2d47c8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -71,7 +71,11 @@
         if (mNextAlarm != null) {
             pw.println(new Date(mNextAlarm.getTriggerTime()));
             pw.print("  PendingIntentPkg=");
-            pw.println(mNextAlarm.getShowIntent().getCreatorPackage());
+            if (mNextAlarm.getShowIntent() != null) {
+                pw.println(mNextAlarm.getShowIntent().getCreatorPackage());
+            } else {
+                pw.println("showIntent=null");
+            }
         } else {
             pw.println("null");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 84d7c05..5d7d480 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -77,7 +77,6 @@
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -204,7 +203,7 @@
         final int stroke = colorized ? mContext.getResources().getDimensionPixelSize(
                 R.dimen.remote_input_view_text_stroke) : 0;
         if (colorized) {
-            final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor);
+            final boolean dark = Notification.Builder.isColorDark(backgroundColor);
             final int foregroundColor = dark ? Color.WHITE : Color.BLACK;
             final int inverseColor = dark ? Color.BLACK : Color.WHITE;
             editBgColor = backgroundColor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 41b1dd1..4e33529 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -628,7 +628,7 @@
         mCurrentBackgroundColor = backgroundColor;
         mCurrentColorized = colorized;
 
-        final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor);
+        final boolean dark = Notification.Builder.isColorDark(backgroundColor);
 
         mCurrentTextColor = ContrastColorUtil.ensureTextContrast(
                 dark ? mDefaultTextColorDarkBg : mDefaultTextColor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt
new file mode 100644
index 0000000..ae9d9ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 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.policy
+
+import android.content.Context
+import android.text.StaticLayout
+import android.util.AttributeSet
+import android.widget.TextView
+import com.android.systemui.R
+
+/**
+ * View for showing a date that can toggle between two different formats depending on size.
+ *
+ * If no pattern can fit, it will display empty.
+ *
+ * @see R.styleable.VariableDateView_longDatePattern
+ * @see R.styleable.VariableDateView_shortDatePattern
+ */
+class VariableDateView(context: Context, attrs: AttributeSet) : TextView(context, attrs) {
+
+    val longerPattern: String
+    val shorterPattern: String
+
+    init {
+        val a = context.theme.obtainStyledAttributes(
+                attrs,
+                R.styleable.VariableDateView,
+                0, 0)
+        longerPattern = a.getString(R.styleable.VariableDateView_longDatePattern)
+                ?: context.getString(R.string.system_ui_date_pattern)
+        shorterPattern = a.getString(R.styleable.VariableDateView_shortDatePattern)
+                ?: context.getString(R.string.abbrev_month_day_no_year)
+
+        a.recycle()
+    }
+
+    /**
+     * Freeze the pattern switching
+     *
+     * Use during animations if the container will change its size but this view should not change
+     */
+    var freezeSwitching = false
+
+    private var onMeasureListener: OnMeasureListener? = null
+
+    fun onAttach(listener: OnMeasureListener?) {
+        onMeasureListener = listener
+    }
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        val availableWidth = MeasureSpec.getSize(widthMeasureSpec) - paddingStart - paddingEnd
+        if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED && !freezeSwitching) {
+            onMeasureListener?.onMeasureAction(availableWidth)
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+    }
+
+    fun getDesiredWidthForText(text: CharSequence): Float {
+        return StaticLayout.getDesiredWidth(text, paint)
+    }
+
+    interface OnMeasureListener {
+        fun onMeasureAction(availableWidth: Int)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
new file mode 100644
index 0000000..99d84c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2021 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.policy
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.icu.text.DateFormat
+import android.icu.text.DisplayContext
+import android.icu.util.Calendar
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.UserHandle
+import android.text.TextUtils
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dependency
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.ViewController
+import com.android.systemui.util.time.SystemClock
+import java.text.FieldPosition
+import java.text.ParsePosition
+import java.util.Date
+import java.util.Locale
+import javax.inject.Inject
+import javax.inject.Named
+
+@VisibleForTesting
+internal fun getTextForFormat(date: Date?, format: DateFormat): String {
+    return if (format === EMPTY_FORMAT) { // Check if same object
+        ""
+    } else format.format(date)
+}
+
+@VisibleForTesting
+internal fun getFormatFromPattern(pattern: String?): DateFormat {
+    if (TextUtils.equals(pattern, "")) {
+        return EMPTY_FORMAT
+    }
+    val l = Locale.getDefault()
+    val format = DateFormat.getInstanceForSkeleton(pattern, l)
+    format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE)
+    return format
+}
+
+private val EMPTY_FORMAT: DateFormat = object : DateFormat() {
+    override fun format(
+        cal: Calendar,
+        toAppendTo: StringBuffer,
+        fieldPosition: FieldPosition
+    ): StringBuffer? {
+        return null
+    }
+
+    override fun parse(text: String, cal: Calendar, pos: ParsePosition) {}
+}
+
+private const val DEBUG = false
+private const val TAG = "VariableDateViewController"
+
+class VariableDateViewController(
+    private val systemClock: SystemClock,
+    private val broadcastDispatcher: BroadcastDispatcher,
+    private val timeTickHandler: Handler,
+    view: VariableDateView
+) : ViewController<VariableDateView>(view) {
+
+    private var dateFormat: DateFormat? = null
+    private var datePattern = view.longerPattern
+        set(value) {
+            if (field == value) return
+            field = value
+            dateFormat = null
+            if (isAttachedToWindow) {
+                post(::updateClock)
+            }
+        }
+    private var lastWidth = Integer.MAX_VALUE
+    private var lastText = ""
+    private var currentTime = Date()
+
+    // View class easy accessors
+    private val longerPattern: String
+        get() = mView.longerPattern
+    private val shorterPattern: String
+        get() = mView.shorterPattern
+    private fun post(block: () -> Unit) = mView.handler?.post(block)
+
+    private val intentReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            // If the handler is null, it means we received a broadcast while the view has not
+            // finished being attached or in the process of being detached.
+            // In that case, do not post anything.
+            val handler = mView.handler ?: return
+            val action = intent.action
+            if (
+                    Intent.ACTION_TIME_TICK == action ||
+                    Intent.ACTION_TIME_CHANGED == action ||
+                    Intent.ACTION_TIMEZONE_CHANGED == action ||
+                    Intent.ACTION_LOCALE_CHANGED == action
+            ) {
+                if (
+                        Intent.ACTION_LOCALE_CHANGED == action ||
+                        Intent.ACTION_TIMEZONE_CHANGED == action
+                ) {
+                    // need to get a fresh date format
+                    handler.post { dateFormat = null }
+                }
+                handler.post(::updateClock)
+            }
+        }
+    }
+
+    private val onMeasureListener = object : VariableDateView.OnMeasureListener {
+        override fun onMeasureAction(availableWidth: Int) {
+            if (availableWidth != lastWidth) {
+                // maybeChangeFormat will post if the pattern needs to change.
+                maybeChangeFormat(availableWidth)
+                lastWidth = availableWidth
+            }
+        }
+    }
+
+    override fun onViewAttached() {
+        val filter = IntentFilter().apply {
+            addAction(Intent.ACTION_TIME_TICK)
+            addAction(Intent.ACTION_TIME_CHANGED)
+            addAction(Intent.ACTION_TIMEZONE_CHANGED)
+            addAction(Intent.ACTION_LOCALE_CHANGED)
+        }
+
+        broadcastDispatcher.registerReceiver(intentReceiver, filter,
+                HandlerExecutor(timeTickHandler), UserHandle.SYSTEM)
+
+        post(::updateClock)
+        mView.onAttach(onMeasureListener)
+    }
+
+    override fun onViewDetached() {
+        dateFormat = null
+        mView.onAttach(null)
+        broadcastDispatcher.unregisterReceiver(intentReceiver)
+    }
+
+    private fun updateClock() {
+        if (dateFormat == null) {
+            dateFormat = getFormatFromPattern(datePattern)
+        }
+
+        currentTime.time = systemClock.currentTimeMillis()
+
+        val text = getTextForFormat(currentTime, dateFormat!!)
+        if (text != lastText) {
+            mView.setText(text)
+            lastText = text
+        }
+    }
+
+    private fun maybeChangeFormat(availableWidth: Int) {
+        if (mView.freezeSwitching ||
+                availableWidth > lastWidth && datePattern == longerPattern ||
+                availableWidth < lastWidth && datePattern == ""
+        ) {
+            // Nothing to do
+            return
+        }
+        if (DEBUG) Log.d(TAG, "Width changed. Maybe changing pattern")
+        // Start with longer pattern and see what fits
+        var text = getTextForFormat(currentTime, getFormatFromPattern(longerPattern))
+        var length = mView.getDesiredWidthForText(text)
+        if (length <= availableWidth) {
+            changePattern(longerPattern)
+            return
+        }
+
+        text = getTextForFormat(currentTime, getFormatFromPattern(shorterPattern))
+        length = mView.getDesiredWidthForText(text)
+        if (length <= availableWidth) {
+            changePattern(shorterPattern)
+            return
+        }
+
+        changePattern("")
+    }
+
+    private fun changePattern(newPattern: String) {
+        if (newPattern.equals(datePattern)) return
+        if (DEBUG) Log.d(TAG, "Changing pattern to $newPattern")
+        datePattern = newPattern
+    }
+
+    class Factory @Inject constructor(
+        private val systemClock: SystemClock,
+        private val broadcastDispatcher: BroadcastDispatcher,
+        @Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler
+    ) {
+        fun create(view: VariableDateView): VariableDateViewController {
+            return VariableDateViewController(
+                    systemClock,
+                    broadcastDispatcher,
+                    handler,
+                    view
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index c3b4fbe..fe0b970 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -132,14 +132,18 @@
     /* Target package for each overlay category. */
     private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>();
     private final OverlayManager mOverlayManager;
-    private final Executor mExecutor;
+    private final Executor mBgExecutor;
+    private final Executor mMainExecutor;
     private final String mLauncherPackage;
     private final String mThemePickerPackage;
 
-    public ThemeOverlayApplier(OverlayManager overlayManager, Executor executor,
+    public ThemeOverlayApplier(OverlayManager overlayManager,
+            Executor bgExecutor,
+            Executor mainExecutor,
             String launcherPackage, String themePickerPackage, DumpManager dumpManager) {
         mOverlayManager = overlayManager;
-        mExecutor = executor;
+        mBgExecutor = bgExecutor;
+        mMainExecutor = mainExecutor;
         mLauncherPackage = launcherPackage;
         mThemePickerPackage = themePickerPackage;
         mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet(
@@ -170,12 +174,13 @@
      * Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that
      * affect sysui will also be applied to the system user.
      */
-    void applyCurrentUserOverlays(
+    public void applyCurrentUserOverlays(
             Map<String, OverlayIdentifier> categoryToPackage,
             FabricatedOverlay[] pendingCreation,
             int currentUser,
-            Set<UserHandle> managedProfiles) {
-        mExecutor.execute(() -> {
+            Set<UserHandle> managedProfiles,
+            Runnable onOverlaysApplied) {
+        mBgExecutor.execute(() -> {
 
             // Disable all overlays that have not been specified in the user setting.
             final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES);
@@ -221,6 +226,7 @@
 
             try {
                 mOverlayManager.commit(transaction.build());
+                mMainExecutor.execute(onOverlaysApplied);
             } catch (SecurityException | IllegalStateException e) {
                 Log.e(TAG, "setEnabled failed", e);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 81999b5..c3327df 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -521,17 +521,21 @@
                     .map(key -> key + " -> " + categoryToPackage.get(key)).collect(
                             Collectors.joining(", ")));
         }
+        Runnable overlaysAppliedRunnable = () -> onOverlaysApplied();
         if (mNeedsOverlayCreation) {
             mNeedsOverlayCreation = false;
             mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
                     mSecondaryOverlay, mNeutralOverlay
-            }, currentUser, managedProfiles);
+            }, currentUser, managedProfiles, overlaysAppliedRunnable);
         } else {
             mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser,
-                    managedProfiles);
+                    managedProfiles, overlaysAppliedRunnable);
         }
     }
 
+    protected void onOverlaysApplied() {
+    }
+
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("mSystemColors=" + mCurrentColors);
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
index 98b4209e..bfa50bc 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
@@ -36,6 +36,7 @@
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.WindowManager;
 import android.widget.CheckBox;
 import android.widget.CompoundButton;
 import android.widget.TextView;
@@ -63,6 +64,8 @@
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
+        getWindow().addPrivateFlags(
+                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
         Intent intent = getIntent();
         mDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
         mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index 90e022a5..bd11039 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -87,15 +87,23 @@
                     && (mLastPrimaryEvent == null
                     || !mLastPrimaryEvent.getBelow()
                     || !event.getBelow())) {
-                mSecondaryThresholdSensor.pause();
+                chooseSensor();
                 if (mLastPrimaryEvent == null || !mLastPrimaryEvent.getBelow()) {
                     // Only check the secondary as long as the primary thinks we're near.
-                    mCancelSecondaryRunnable = null;
+                    if (mCancelSecondaryRunnable != null) {
+                        mCancelSecondaryRunnable.run();
+                        mCancelSecondaryRunnable = null;
+                    }
                     return;
                 } else {
                     // Check this sensor again in a moment.
-                    mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(
-                            mSecondaryThresholdSensor::resume, SECONDARY_PING_INTERVAL_MS);
+                    mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(() -> {
+                        // This is safe because we know that mSecondaryThresholdSensor
+                        // is loaded, otherwise we wouldn't be here.
+                        mPrimaryThresholdSensor.pause();
+                        mSecondaryThresholdSensor.resume();
+                    },
+                        SECONDARY_PING_INTERVAL_MS);
                 }
             }
             logDebug("Secondary sensor event: " + event.getBelow() + ".");
@@ -159,12 +167,8 @@
      * of what is reported by the primary sensor.
      */
     public void setSecondarySafe(boolean safe) {
-        mSecondarySafe = safe;
-        if (!mSecondarySafe) {
-            mSecondaryThresholdSensor.pause();
-        } else {
-            mSecondaryThresholdSensor.resume();
-        }
+        mSecondarySafe = mSecondaryThresholdSensor.isLoaded() && safe;
+        chooseSensor();
     }
 
     /**
@@ -209,16 +213,30 @@
             return;
         }
         if (!mInitializedListeners) {
+            mPrimaryThresholdSensor.pause();
+            mSecondaryThresholdSensor.pause();
             mPrimaryThresholdSensor.register(mPrimaryEventListener);
-            if (!mSecondarySafe) {
-                mSecondaryThresholdSensor.pause();
-            }
             mSecondaryThresholdSensor.register(mSecondaryEventListener);
             mInitializedListeners = true;
         }
         logDebug("Registering sensor listener");
-        mPrimaryThresholdSensor.resume();
+
         mRegistered = true;
+        chooseSensor();
+    }
+
+    private void chooseSensor() {
+        mExecution.assertIsMainThread();
+        if (!mRegistered || mPaused || mListeners.isEmpty()) {
+            return;
+        }
+        if (mSecondarySafe) {
+            mSecondaryThresholdSensor.resume();
+            mPrimaryThresholdSensor.pause();
+        } else {
+            mPrimaryThresholdSensor.resume();
+            mSecondaryThresholdSensor.pause();
+        }
     }
 
     /**
@@ -312,7 +330,7 @@
         }
 
         if (!mSecondarySafe && !event.getBelow()) {
-            mSecondaryThresholdSensor.pause();
+            chooseSensor();
         }
 
         mLastEvent = event;
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 5f1fe92..763a5cb 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -93,6 +93,13 @@
         <activity android:name="com.android.systemui.screenshot.RecyclerViewActivity"
                   android:exported="false" />
 
+        <!-- started from UsbDeviceSettingsManager -->
+        <activity android:name=".usb.UsbPermissionActivityTest$UsbPermissionActivityTestable"
+                  android:exported="false"
+                  android:theme="@style/Theme.SystemUI.Dialog.Alert"
+                  android:finishOnCloseSystemDialogs="true"
+                  android:excludeFromRecents="true" />
+
         <provider
             android:name="androidx.startup.InitializationProvider"
             tools:replace="android:authorities"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 39d5314..8dd5d6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -565,8 +565,9 @@
                 credentialAllowed,
                 true /* requireConfirmation */,
                 0 /* userId */,
-                "testPackage",
                 0 /* operationId */,
+                "testPackage",
+                1 /* requestId */,
                 BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT);
     }
 
@@ -612,7 +613,7 @@
         @Override
         protected AuthDialog buildDialog(PromptInfo promptInfo,
                 boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed,
-                String opPackageName, boolean skipIntro, long operationId,
+                String opPackageName, boolean skipIntro, long operationId, long requestId,
                 @BiometricManager.BiometricMultiSensorMode int multiSensorConfig) {
 
             mLastBiometricPromptInfo = promptInfo;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 2120b0e..cff2aed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.biometrics;
 
+import static android.media.AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
+
 import static junit.framework.Assert.assertEquals;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -69,6 +71,7 @@
 import com.android.systemui.util.concurrency.FakeExecution;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.util.time.SystemClock;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -147,6 +150,8 @@
     private Handler mHandler;
     @Mock
     private ConfigurationController mConfigurationController;
+    @Mock
+    private SystemClock mSystemClock;
 
     private FakeExecutor mFgExecutor;
 
@@ -229,7 +234,8 @@
                 mKeyguardBypassController,
                 mDisplayManager,
                 mHandler,
-                mConfigurationController);
+                mConfigurationController,
+                mSystemClock);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -413,6 +419,21 @@
     }
 
     @Test
+    public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException {
+        // GIVEN overlay was showing and the udfps bouncer is showing
+        mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
+                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+        when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
+
+        // WHEN the overlay is hidden
+        mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+        mFgExecutor.runAllReady();
+
+        // THEN the udfps bouncer is reset
+        verify(mStatusBarKeyguardViewManager).resetAlternateAuth(eq(true));
+    }
+
+    @Test
     public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
                 IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
@@ -544,5 +565,10 @@
                 eq(mUdfpsController.EFFECT_CLICK),
                 eq("udfps-onStart"),
                 eq(UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES));
+
+        // THEN make sure vibration attributes has so that it always will play the haptic,
+        // even in battery saver mode
+        assertEquals(USAGE_ASSISTANCE_ACCESSIBILITY,
+                UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES.getUsage());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 5eb9138..897ac27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -18,9 +18,9 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeast;
-
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -46,6 +46,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -89,6 +90,7 @@
     private ConfigurationController mConfigurationController;
     @Mock
     private UdfpsController mUdfpsController;
+    private FakeSystemClock mSystemClock = new FakeSystemClock();
 
     private UdfpsKeyguardViewController mController;
 
@@ -124,6 +126,7 @@
                 mKeyguardViewMediator,
                 mLockscreenShadeTransitionController,
                 mConfigurationController,
+                mSystemClock,
                 mKeyguardStateController,
                 mUdfpsController);
     }
@@ -299,6 +302,59 @@
     }
 
     @Test
+    public void testHiddenUdfpsBouncerOnTouchOutside_nothingHappens() {
+        // GIVEN view is attached
+        mController.onViewAttached();
+        captureAltAuthInterceptor();
+
+        // GIVEN udfps bouncer isn't showing
+        mAltAuthInterceptor.hideAlternateAuthBouncer();
+
+        // WHEN touch is observed outside the view
+        mController.onTouchOutsideView();
+
+        // THEN bouncer / alt auth methods are never called
+        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
+    }
+
+    @Test
+    public void testShowingUdfpsBouncerOnTouchOutsideWithinThreshold_nothingHappens() {
+        // GIVEN view is attached
+        mController.onViewAttached();
+        captureAltAuthInterceptor();
+
+        // GIVEN udfps bouncer is showing
+        mAltAuthInterceptor.showAlternateAuthBouncer();
+
+        // WHEN touch is observed outside the view 200ms later (just within threshold)
+        mSystemClock.advanceTime(200);
+        mController.onTouchOutsideView();
+
+        // THEN bouncer / alt auth methods are never called because not enough time has passed
+        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
+    }
+
+    @Test
+    public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showInputBouncer() {
+        // GIVEN view is attached
+        mController.onViewAttached();
+        captureAltAuthInterceptor();
+
+        // GIVEN udfps bouncer is showing
+        mAltAuthInterceptor.showAlternateAuthBouncer();
+
+        // WHEN touch is observed outside the view 205ms later
+        mSystemClock.advanceTime(205);
+        mController.onTouchOutsideView();
+
+        // THEN show the bouncer and reset alt auth
+        verify(mStatusBarKeyguardViewManager).showBouncer(eq(true));
+        verify(mStatusBarKeyguardViewManager).resetAlternateAuth(anyBoolean());
+    }
+
+    @Test
     public void testFadeInWithStatusBarExpansion() {
         // GIVEN view is attached
         mController.onViewAttached();
@@ -328,7 +384,41 @@
         // WHEN status bar expansion is 0 but udfps bouncer is requested
         mAltAuthInterceptor.showAlternateAuthBouncer();
 
-        // THEN alpha is 0
+        // THEN alpha is 255
+        verify(mView).setUnpausedAlpha(255);
+    }
+
+    @Test
+    public void testTransitionToFullShadeProgress() {
+        // GIVEN view is attached and status bar expansion is 1f
+        mController.onViewAttached();
+        captureExpansionListeners();
+        updateStatusBarExpansion(1f, true);
+        reset(mView);
+
+        // WHEN we're transitioning to the full shade
+        float transitionProgress = .6f;
+        mController.setTransitionToFullShadeProgress(transitionProgress);
+
+        // THEN alpha is between 0 and 255
+        verify(mView).setUnpausedAlpha((int) ((1f - transitionProgress) * 255));
+    }
+
+    @Test
+    public void testShowUdfpsBouncer_transitionToFullShadeProgress() {
+        // GIVEN view is attached and status bar expansion is 1f
+        mController.onViewAttached();
+        captureExpansionListeners();
+        captureKeyguardStateControllerCallback();
+        captureAltAuthInterceptor();
+        updateStatusBarExpansion(1f, true);
+        mAltAuthInterceptor.showAlternateAuthBouncer();
+        reset(mView);
+
+        // WHEN we're transitioning to the full shade
+        mController.setTransitionToFullShadeProgress(1.0f);
+
+        // THEN alpha is 255 (b/c udfps bouncer is requested)
         verify(mView).setUnpausedAlpha(255);
     }
 
@@ -361,6 +451,8 @@
         mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
     }
 
+
+
     private void captureKeyguardStateControllerCallback() {
         verify(mKeyguardStateController).addCallback(
                 mKeyguardStateControllerCallbackCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 4e8b59c..e0520b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.doze.DozeMachine.State.DOZE;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
@@ -29,6 +30,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -42,11 +44,11 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
-import android.view.Display;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -82,6 +84,8 @@
     WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
     DozeParameters mDozeParameters;
+    @Mock
+    DockManager mDockManager;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor);
 
@@ -109,9 +113,7 @@
         mSensor = fakeSensorManager.getFakeLightSensor();
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
                 Optional.of(mSensor.getSensor()), mDozeHost, null /* handler */,
-                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters);
-
-        mScreen.onScreenState(Display.STATE_ON);
+                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager);
     }
 
     @Test
@@ -123,18 +125,9 @@
     }
 
     @Test
-    public void testAod_usesLightSensor() {
-        mScreen.onScreenState(Display.STATE_DOZE);
-        waitForSensorManager();
-
-        mSensor.sendSensorEvent(3);
-
-        assertEquals(3, mServiceFake.screenBrightness);
-    }
-
-    @Test
     public void testAod_usesDebugValue() throws Exception {
-        mScreen.onScreenState(Display.STATE_DOZE);
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
         waitForSensorManager();
 
         Intent intent = new Intent(DozeScreenBrightness.ACTION_AOD_BRIGHTNESS);
@@ -157,10 +150,53 @@
     }
 
     @Test
+    public void doze_doesNotUseLightSensor() {
+        // GIVEN the device is docked and the display state changes to ON
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE);
+        waitForSensorManager();
+
+        // WHEN new sensor event sent
+        mSensor.sendSensorEvent(3);
+
+        // THEN brightness is NOT changed, it's set to the default brightness
+        assertNotSame(3, mServiceFake.screenBrightness);
+        assertEquals(DEFAULT_BRIGHTNESS, mServiceFake.screenBrightness);
+    }
+
+    @Test
+    public void aod_usesLightSensor() {
+        // GIVEN the device is docked and the display state changes to ON
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        waitForSensorManager();
+
+        // WHEN new sensor event sent
+        mSensor.sendSensorEvent(3);
+
+        // THEN brightness is updated
+        assertEquals(3, mServiceFake.screenBrightness);
+    }
+
+    @Test
+    public void docked_usesLightSensor() {
+        // GIVEN the device is docked and the display state changes to ON
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        mScreen.transitionTo(DOZE_AOD, DOZE_AOD_DOCKED);
+        waitForSensorManager();
+
+        // WHEN new sensor event sent
+        mSensor.sendSensorEvent(3);
+
+        // THEN brightness is updated
+        assertEquals(3, mServiceFake.screenBrightness);
+    }
+
+    @Test
     public void testPausingAod_doesNotResetBrightness() throws Exception {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-        mScreen.onScreenState(Display.STATE_DOZE);
         waitForSensorManager();
 
         mSensor.sendSensorEvent(1);
@@ -175,7 +211,7 @@
     public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception {
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
                 Optional.empty() /* sensor */, mDozeHost, null /* handler */,
-                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters);
+                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager);
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
         reset(mDozeHost);
@@ -201,36 +237,21 @@
     }
 
     @Test
-    public void testOnScreenStateSetBeforeTransition_stillRegistersSensor() {
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.onScreenState(Display.STATE_DOZE);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-        waitForSensorManager();
-
-        mSensor.sendSensorEvent(1);
-
-        assertEquals(1, mServiceFake.screenBrightness);
-    }
-
-    @Test
     public void testNullSensor() throws Exception {
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
                 Optional.empty() /* sensor */, mDozeHost, null /* handler */,
-                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters);
+                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
         mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
         mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
-        mScreen.onScreenState(Display.STATE_DOZE);
-        mScreen.onScreenState(Display.STATE_OFF);
     }
 
     @Test
     public void testNoBrightnessDeliveredAfterFinish() throws Exception {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-        mScreen.onScreenState(Display.STATE_DOZE);
         mScreen.transitionTo(DOZE_AOD, FINISH);
         waitForSensorManager();
 
@@ -243,7 +264,6 @@
     public void testNonPositiveBrightness_keepsPreviousBrightnessAndScrim() {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-        mScreen.onScreenState(Display.STATE_DOZE);
         waitForSensorManager();
 
         mSensor.sendSensorEvent(1);
@@ -257,7 +277,6 @@
     public void pausingAod_unblanksAfterSensor() {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-        mScreen.onScreenState(Display.STATE_DOZE);
         waitForSensorManager();
 
         mSensor.sendSensorEvent(2);
@@ -269,7 +288,6 @@
 
         reset(mDozeHost);
         mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
-        mScreen.onScreenState(Display.STATE_DOZE);
         waitForSensorManager();
         mSensor.sendSensorEvent(2);
         verify(mDozeHost).setAodDimmingScrim(eq(0f));
@@ -279,7 +297,6 @@
     public void pausingAod_unblanksIfSensorWasAlwaysReady() throws Exception {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-        mScreen.onScreenState(Display.STATE_DOZE);
         waitForSensorManager();
 
         mSensor.sendSensorEvent(2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index 41d7fd6..150ab77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE;
 import static com.android.systemui.doze.DozeMachine.State.FINISH;
@@ -34,6 +35,7 @@
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -45,6 +47,8 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.UdfpsController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.wakelock.WakeLockFake;
 import com.android.systemui.utils.os.FakeHandler;
@@ -56,6 +60,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import javax.inject.Provider;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class DozeScreenStateTest extends SysuiTestCase {
@@ -68,17 +74,29 @@
     private DozeParameters mDozeParameters;
     private WakeLockFake mWakeLock;
     private DozeScreenState mScreen;
+    @Mock
+    private Provider<UdfpsController> mUdfpsControllerProvider;
+    @Mock
+    private AuthController mAuthController;
+    @Mock
+    private UdfpsController mUdfpsController;
+    @Mock
+    private DozeLog mDozeLog;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+        when(mUdfpsControllerProvider.get()).thenReturn(mUdfpsController);
+        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
+        when(mUdfpsController.isFingerDown()).thenReturn(false);
+
         mServiceFake = new DozeServiceFake();
         mHandlerFake = new FakeHandler(Looper.getMainLooper());
         mWakeLock = new WakeLockFake();
         mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters,
-                mWakeLock);
+                mWakeLock, mAuthController, mUdfpsControllerProvider, mDozeLog);
     }
 
     @Test
@@ -233,4 +251,56 @@
         assertEquals(Display.STATE_OFF, mServiceFake.screenState);
     }
 
+    @Test
+    public void testDelayEnterDozeScreenState_whenUdfpsFingerDown() {
+        // GIVEN AOD is initialized
+        when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
+        mHandlerFake.setMode(QUEUEING);
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mHandlerFake.dispatchQueuedMessages();
+
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+        // WHEN udfps is activated (fingerDown)
+        when(mUdfpsController.isFingerDown()).thenReturn(true);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // THEN the display screen state doesn't immediately change
+        assertEquals(Display.STATE_ON, mServiceFake.screenState);
+
+        // WHEN udfpsController finger is no longer down and the queued messages are run
+        when(mUdfpsController.isFingerDown()).thenReturn(false);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // THEN the display screen state will change
+        assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
+    }
+
+    @Test
+    public void testDelayExitPulsingScreenState_whenUdfpsFingerDown() {
+        // GIVEN we're pulsing
+        when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
+        mHandlerFake.setMode(QUEUEING);
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        mScreen.transitionTo(DOZE_AOD, DOZE_REQUEST_PULSE);
+        mScreen.transitionTo(DOZE_REQUEST_PULSE, DOZE_PULSING);
+        mScreen.transitionTo(DOZE_PULSING, DOZE_PULSE_DONE);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // WHEN udfps is activated while are transitioning back to DOZE_AOD
+        mScreen.transitionTo(DOZE_PULSE_DONE, DOZE_AOD);
+        when(mUdfpsController.isFingerDown()).thenReturn(true);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // THEN the display screen state doesn't immediately change
+        assertEquals(Display.STATE_ON, mServiceFake.screenState);
+
+        // WHEN udfpsController finger is no longer down and the queued messages are run
+        when(mUdfpsController.isFingerDown()).thenReturn(false);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // THEN the display screen state will change
+        assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 0c94f09..5c4c27c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -88,7 +88,7 @@
     private FakeSettings mFakeSettings = new FakeSettings();
     private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
     private TestableLooper mTestableLooper;
-    private DozeSensors mDozeSensors;
+    private TestableDozeSensors mDozeSensors;
     private TriggerSensor mSensorTap;
 
     @Before
@@ -170,6 +170,94 @@
         assertTrue(mSensorTap.mRequested);
     }
 
+    @Test
+    public void testDozeSensorSetListening() {
+        // GIVEN doze sensors enabled
+        when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+        // GIVEN a trigger sensor
+        Sensor mockSensor = mock(Sensor.class);
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+                mockSensor,
+                /* settingEnabled */ true,
+                /* requiresTouchScreen */ true);
+        when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+                .thenReturn(true);
+
+        // WHEN we want to listen for the trigger sensor
+        triggerSensor.setListening(true);
+
+        // THEN the sensor is registered
+        assertTrue(triggerSensor.mRegistered);
+    }
+
+    @Test
+    public void testDozeSensorSettingDisabled() {
+        // GIVEN doze sensors enabled
+        when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+        // GIVEN a trigger sensor
+        Sensor mockSensor = mock(Sensor.class);
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+                mockSensor,
+                /* settingEnabled*/ false,
+                /* requiresTouchScreen */ true);
+        when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+                .thenReturn(true);
+
+        // WHEN setListening is called
+        triggerSensor.setListening(true);
+
+        // THEN the sensor is not registered
+        assertFalse(triggerSensor.mRegistered);
+    }
+
+    @Test
+    public void testDozeSensorIgnoreSetting() {
+        // GIVEN doze sensors enabled
+        when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+        // GIVEN a trigger sensor that's
+        Sensor mockSensor = mock(Sensor.class);
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+                mockSensor,
+                /* settingEnabled*/ false,
+                /* requiresTouchScreen */ true);
+        when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+                .thenReturn(true);
+
+        // GIVEN sensor is listening
+        triggerSensor.setListening(true);
+
+        // WHEN ignoreSetting is called
+        triggerSensor.ignoreSetting(true);
+
+        // THEN the sensor is registered
+        assertTrue(triggerSensor.mRegistered);
+    }
+
+    @Test
+    public void testUpdateListeningAfterAlreadyRegistered() {
+        // GIVEN doze sensors enabled
+        when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+        // GIVEN a trigger sensor
+        Sensor mockSensor = mock(Sensor.class);
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+                mockSensor,
+                /* settingEnabled*/ true,
+                /* requiresTouchScreen */ true);
+        when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+                .thenReturn(true);
+
+        // WHEN setListening is called AND updateListening is called
+        triggerSensor.setListening(true);
+        triggerSensor.updateListening();
+
+        // THEN the sensor is still registered
+        assertTrue(triggerSensor.mRegistered);
+    }
+
     private class TestableDozeSensors extends DozeSensors {
 
         TestableDozeSensors() {
@@ -187,5 +275,17 @@
             }
             mSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap};
         }
+
+        public TriggerSensor createDozeSensor(Sensor sensor, boolean settingEnabled,
+                boolean requiresTouchScreen) {
+            return new TriggerSensor(/* sensor */ sensor,
+                    /* setting name */ "test_setting",
+                    /* settingDefault */ settingEnabled,
+                    /* configured */ true,
+                    /* pulseReason*/ 0,
+                    /* reportsTouchCoordinate*/ false,
+                    requiresTouchScreen,
+                    mDozeLog);
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 578c2d9..509ef82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -51,6 +52,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -112,6 +114,7 @@
     @Mock private Handler mHandler;
     @Mock private UserContextProvider mUserContextProvider;
     @Mock private StatusBar mStatusBar;
+    @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     private TestableLooper mTestableLooper;
 
@@ -156,7 +159,8 @@
                 mSysUiState,
                 mHandler,
                 mPackageManager,
-                mStatusBar
+                mStatusBar,
+                mKeyguardUpdateMonitor
         );
         mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
 
@@ -422,4 +426,31 @@
         restartAction.onLongPress();
         verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_REBOOT_LONG_PRESS);
     }
+
+    @Test
+    public void testOnLockScreen_disableSmartLock() {
+        mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+        int user = KeyguardUpdateMonitor.getCurrentUser();
+        doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+        doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+        doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+        doReturn(false).when(mStatusBar).isKeyguardShowing();
+        String[] actions = {
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
+                GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+        };
+        doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+
+        // When entering power menu from lockscreen, with smart lock enabled
+        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+        mGlobalActionsDialogLite.showOrHideDialog(true, true);
+
+        // Then smart lock will be disabled
+        verify(mLockPatternUtils).requireCredentialEntry(eq(user));
+
+        // hide dialog again
+        mGlobalActionsDialogLite.showOrHideDialog(true, true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 2fa67cc..338bb30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -56,6 +56,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -126,6 +127,7 @@
     @Mock private PackageManager mPackageManager;
     @Mock private SecureSettings mSecureSettings;
     @Mock private StatusBar mStatusBar;
+    @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     private TestableLooper mTestableLooper;
 
@@ -169,7 +171,8 @@
                 mSysUiState,
                 mHandler,
                 mPackageManager,
-                mStatusBar
+                mStatusBar,
+                mKeyguardUpdateMonitor
         );
         mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index ad08780..31d70f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -67,7 +67,7 @@
 import org.mockito.MockitoAnnotations;
 
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@TestableLooper.RunWithLooper
 @SmallTest
 public class KeyguardViewMediatorTest extends SysuiTestCase {
     private KeyguardViewMediator mViewMediator;
@@ -126,7 +126,6 @@
                 mUnlockedScreenOffAnimationController,
                 () -> mNotificationShadeDepthController);
         mViewMediator.start();
-        mViewMediator.onSystemReady();
     }
 
     @Test
@@ -165,8 +164,10 @@
     }
 
     @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void restoreBouncerWhenSimLockedAndKeyguardIsGoingAway() {
         // When showing and provisioned
+        mViewMediator.onSystemReady();
         when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true);
         mViewMediator.setShowingLocked(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
new file mode 100644
index 0000000..d279bbb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2021 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.keyguard;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.os.Vibrator;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardViewController;
+import com.android.keyguard.LockIconView;
+import com.android.keyguard.LockIconViewController;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class LockIconViewControllerTest extends SysuiTestCase {
+    private @Mock LockIconView mLockIconView;
+    private @Mock Context mContext;
+    private @Mock Resources mResources;
+    private @Mock DisplayMetrics mDisplayMetrics;
+    private @Mock StatusBarStateController mStatusBarStateController;
+    private @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private @Mock KeyguardViewController mKeyguardViewController;
+    private @Mock KeyguardStateController mKeyguardStateController;
+    private @Mock FalsingManager mFalsingManager;
+    private @Mock AuthController mAuthController;
+    private @Mock DumpManager mDumpManager;
+    private @Mock AccessibilityManager mAccessibilityManager;
+    private @Mock ConfigurationController mConfigurationController;
+    private @Mock DelayableExecutor mDelayableExecutor;
+    private @Mock Vibrator mVibrator;
+    private @Mock AuthRippleController mAuthRippleController;
+
+    private LockIconViewController mLockIconViewController;
+
+    // Capture listeners so that they can be used to send events
+    @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
+            ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+    private View.OnAttachStateChangeListener mAttachListener;
+
+    @Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
+    private AuthController.Callback mAuthControllerCallback;
+
+    @Captor private ArgumentCaptor<PointF> mPointCaptor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mLockIconView.getResources()).thenReturn(mResources);
+        when(mLockIconView.getContext()).thenReturn(mContext);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
+
+        mLockIconViewController = new LockIconViewController(
+                mLockIconView,
+                mStatusBarStateController,
+                mKeyguardUpdateMonitor,
+                mKeyguardViewController,
+                mKeyguardStateController,
+                mFalsingManager,
+                mAuthController,
+                mDumpManager,
+                mAccessibilityManager,
+                mConfigurationController,
+                mDelayableExecutor,
+                mVibrator,
+                mAuthRippleController
+        );
+    }
+
+    @Test
+    public void testUpdateFingerprintLocationOnInit() {
+        // GIVEN fp sensor location is available pre-init
+        final PointF udfpsLocation = new PointF(50, 75);
+        final int radius = 33;
+        final FingerprintSensorPropertiesInternal fpProps =
+                new FingerprintSensorPropertiesInternal(
+                        /* sensorId */ 0,
+                        /* strength */ 0,
+                        /* max enrollments per user */ 5,
+                        /* component info */ new ArrayList<>(),
+                        /* sensorType */ 3,
+                        /* resetLockoutRequiresHwToken */ false,
+                        (int) udfpsLocation.x, (int) udfpsLocation.y, radius);
+        when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
+        when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));
+
+        // WHEN lock icon view controller is initialized and attached
+        mLockIconViewController.init();
+        captureAttachListener();
+        mAttachListener.onViewAttachedToWindow(null);
+
+        // THEN lock icon view location is updated with the same coordinates as fpProps
+        verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius));
+        assertEquals(udfpsLocation, mPointCaptor.getValue());
+    }
+
+    @Test
+    public void testUpdateFingerprintLocationOnAuthenticatorsRegistered() {
+        // GIVEN fp sensor location is not available pre-init
+        when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
+        when(mAuthController.getUdfpsProps()).thenReturn(null);
+        mLockIconViewController.init();
+
+        // GIVEN fp sensor location is available post-init
+        captureAuthControllerCallback();
+        final PointF udfpsLocation = new PointF(50, 75);
+        final int radius = 33;
+        final FingerprintSensorPropertiesInternal fpProps =
+                new FingerprintSensorPropertiesInternal(
+                        /* sensorId */ 0,
+                        /* strength */ 0,
+                        /* max enrollments per user */ 5,
+                        /* component info */ new ArrayList<>(),
+                        /* sensorType */ 3,
+                        /* resetLockoutRequiresHwToken */ false,
+                        (int) udfpsLocation.x, (int) udfpsLocation.y, radius);
+        when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
+        when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));
+
+        // WHEN all authenticators are registered
+        mAuthControllerCallback.onAllAuthenticatorsRegistered();
+
+        // THEN lock icon view location is updated with the same coordinates as fpProps
+        verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius));
+        assertEquals(udfpsLocation, mPointCaptor.getValue());
+    }
+
+    private void captureAuthControllerCallback() {
+        verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
+        mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
+    }
+
+    private void captureAttachListener() {
+        verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture());
+        mAttachListener = mAttachCaptor.getValue();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index b129fdd..42629f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -37,6 +37,7 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.LiveData
 import androidx.test.filters.SmallTest
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.media.dialog.MediaOutputDialogFactory
 import com.android.systemui.plugins.ActivityStarter
@@ -101,7 +102,6 @@
     private lateinit var seamless: ViewGroup
     private lateinit var seamlessIcon: ImageView
     private lateinit var seamlessText: TextView
-    private lateinit var seamlessFallback: ImageView
     private lateinit var seekBar: SeekBar
     private lateinit var elapsedTimeView: TextView
     private lateinit var totalTimeView: TextView
@@ -154,8 +154,6 @@
         whenever(holder.seamlessIcon).thenReturn(seamlessIcon)
         seamlessText = TextView(context)
         whenever(holder.seamlessText).thenReturn(seamlessText)
-        seamlessFallback = ImageView(context)
-        whenever(holder.seamlessFallback).thenReturn(seamlessFallback)
         seekBar = SeekBar(context)
         whenever(holder.seekBar).thenReturn(seekBar)
         elapsedTimeView = TextView(context)
@@ -239,21 +237,19 @@
     @Test
     fun bindDisabledDevice() {
         seamless.id = 1
-        seamlessFallback.id = 2
+        val fallbackString = context.getString(R.string.media_seamless_other_device)
         player.attachPlayer(holder)
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
         player.bindPlayer(state, PACKAGE)
-        verify(expandedSet).setVisibility(seamless.id, View.GONE)
-        verify(expandedSet).setVisibility(seamlessFallback.id, View.VISIBLE)
-        verify(collapsedSet).setVisibility(seamless.id, View.GONE)
-        verify(collapsedSet).setVisibility(seamlessFallback.id, View.VISIBLE)
+        assertThat(seamless.isEnabled()).isFalse()
+        assertThat(seamlessText.getText()).isEqualTo(fallbackString)
+        assertThat(seamless.contentDescription).isEqualTo(fallbackString)
     }
 
     @Test
     fun bindNullDevice() {
-        val fallbackString = context.getResources().getString(
-                com.android.internal.R.string.ext_media_seamless_action)
+        val fallbackString = context.getResources().getString(R.string.media_seamless_other_device)
         player.attachPlayer(holder)
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index ba6dfd3..5c3108c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -163,7 +163,7 @@
     }
 
     @Test
-    fun testSetTimedOut_deactivatesMedia() {
+    fun testSetTimedOut_active_deactivatesMedia() {
         val data = MediaData(userId = USER_ID, initialized = true, backgroundColor = 0, app = null,
                 appIcon = null, artist = null, song = null, artwork = null, actions = emptyList(),
                 actionsToShowInCompact = emptyList(), packageName = "INVALID", token = null,
@@ -176,6 +176,25 @@
     }
 
     @Test
+    fun testSetTimedOut_resume_dismissesMedia() {
+        // WHEN resume controls are present, and time out
+        val desc = MediaDescription.Builder().run {
+            setTitle(SESSION_TITLE)
+            build()
+        }
+        mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
+                APP_NAME, pendingIntent, PACKAGE_NAME)
+        backgroundExecutor.runAllReady()
+        foregroundExecutor.runAllReady()
+        mediaDataManager.setTimedOut(PACKAGE_NAME, timedOut = true)
+
+        // THEN it is removed and listeners are informed
+        foregroundExecutor.advanceClockToLast()
+        foregroundExecutor.runAllReady()
+        verify(listener).onMediaDataRemoved(PACKAGE_NAME)
+    }
+
+    @Test
     fun testLoadsMetadataOnBackground() {
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         assertThat(backgroundExecutor.numPending()).isEqualTo(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
index 150f4545..359746b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
@@ -91,10 +91,12 @@
 
     @Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback>
     @Captor lateinit var actionCaptor: ArgumentCaptor<Runnable>
+    @Captor lateinit var componentCaptor: ArgumentCaptor<String>
 
     private lateinit var executor: FakeExecutor
     private lateinit var data: MediaData
     private lateinit var resumeListener: MediaResumeListener
+    private val clock = FakeSystemClock()
 
     private var originalQsSetting = Settings.Global.getInt(context.contentResolver,
         Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
@@ -122,9 +124,9 @@
         whenever(mockContext.packageManager).thenReturn(context.packageManager)
         whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
 
-        executor = FakeExecutor(FakeSystemClock())
+        executor = FakeExecutor(clock)
         resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
-                tunerService, resumeBrowserFactory, dumpManager)
+                tunerService, resumeBrowserFactory, dumpManager, clock)
         resumeListener.setManager(mediaDataManager)
         mediaDataManager.addListener(resumeListener)
 
@@ -163,7 +165,7 @@
 
         // When listener is created, we do NOT register a user change listener
         val listener = MediaResumeListener(context, broadcastDispatcher, executor, tunerService,
-                resumeBrowserFactory, dumpManager)
+                resumeBrowserFactory, dumpManager, clock)
         listener.setManager(mediaDataManager)
         verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver),
             any(), any(), any())
@@ -328,4 +330,109 @@
         // Then we call restart
         verify(resumeBrowser).restart()
     }
+
+    @Test
+    fun testOnUserUnlock_missingTime_saves() {
+        val currentTime = clock.currentTimeMillis()
+
+        // When resume components without a last played time are loaded
+        testOnUserUnlock_loadsTracks()
+
+        // Then we save an update with the current time
+        verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
+        componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
+                ?.dropLastWhile { it.isEmpty() }.forEach {
+            val result = it.split("/")
+            assertThat(result.size).isEqualTo(3)
+            assertThat(result[2].toLong()).isEqualTo(currentTime)
+        }
+        verify(sharedPrefsEditor, times(1)).apply()
+    }
+
+    @Test
+    fun testLoadComponents_recentlyPlayed_adds() {
+        // Set up browser to return successfully
+        val description = MediaDescription.Builder().setTitle(TITLE).build()
+        val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+        whenever(resumeBrowser.token).thenReturn(token)
+        whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)
+        whenever(resumeBrowser.findRecentMedia()).thenAnswer {
+            callbackCaptor.value.addTrack(description, component, resumeBrowser)
+        }
+
+        // Set up shared preferences to have a component with a recent lastplayed time
+        val lastPlayed = clock.currentTimeMillis()
+        val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
+        whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
+        val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
+                tunerService, resumeBrowserFactory, dumpManager, clock)
+        resumeListener.setManager(mediaDataManager)
+        mediaDataManager.addListener(resumeListener)
+
+        // When we load a component that was played recently
+        val intent = Intent(Intent.ACTION_USER_UNLOCKED)
+        resumeListener.userChangeReceiver.onReceive(mockContext, intent)
+
+        // We add its resume controls
+        verify(resumeBrowser, times(1)).findRecentMedia()
+        verify(mediaDataManager, times(1)).addResumptionControls(anyInt(),
+                any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
+    }
+
+    @Test
+    fun testLoadComponents_old_ignores() {
+        // Set up shared preferences to have a component with an old lastplayed time
+        val lastPlayed = clock.currentTimeMillis() - RESUME_MEDIA_TIMEOUT - 100
+        val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
+        whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
+        val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
+                tunerService, resumeBrowserFactory, dumpManager, clock)
+        resumeListener.setManager(mediaDataManager)
+        mediaDataManager.addListener(resumeListener)
+
+        // When we load a component that is not recent
+        val intent = Intent(Intent.ACTION_USER_UNLOCKED)
+        resumeListener.userChangeReceiver.onReceive(mockContext, intent)
+
+        // We do not try to add resume controls
+        verify(resumeBrowser, times(0)).findRecentMedia()
+        verify(mediaDataManager, times(0)).addResumptionControls(anyInt(),
+                any(), any(), any(), any(), any(), any())
+    }
+
+    @Test
+    fun testOnLoad_hasService_updatesLastPlayed() {
+        // Set up browser to return successfully
+        val description = MediaDescription.Builder().setTitle(TITLE).build()
+        val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+        whenever(resumeBrowser.token).thenReturn(token)
+        whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)
+        whenever(resumeBrowser.findRecentMedia()).thenAnswer {
+            callbackCaptor.value.addTrack(description, component, resumeBrowser)
+        }
+
+        // Set up shared preferences to have a component with a lastplayed time
+        val currentTime = clock.currentTimeMillis()
+        val lastPlayed = currentTime - 1000
+        val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
+        whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
+        val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
+                tunerService, resumeBrowserFactory, dumpManager, clock)
+        resumeListener.setManager(mediaDataManager)
+        mediaDataManager.addListener(resumeListener)
+
+        // When media data is loaded that has not been checked yet, and does have a MBS
+        val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false)
+        resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
+
+        // Then we store the new lastPlayed time
+        verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
+        componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
+                ?.dropLastWhile { it.isEmpty() }.forEach {
+                    val result = it.split("/")
+                    assertThat(result.size).isEqualTo(3)
+                    assertThat(result[2].toLong()).isEqualTo(currentTime)
+                }
+        verify(sharedPrefsEditor, times(1)).apply()
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index 0a573cd6..de2235d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -71,6 +71,7 @@
     private lateinit var playbackBuilder: PlaybackState.Builder
     private lateinit var session: MediaSession
     private lateinit var mediaData: MediaData
+    private lateinit var resumeData: MediaData
     private lateinit var mediaTimeoutListener: MediaTimeoutListener
 
     @Before
@@ -97,6 +98,10 @@
         mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
             emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
             device = null, active = true, resumeAction = null)
+
+        resumeData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
+                emptyList(), emptyList(), PACKAGE, null, clickIntent = null,
+                device = null, active = false, resumeAction = null, resumption = true)
     }
 
     @Test
@@ -120,6 +125,7 @@
         verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
         assertThat(executor.numPending()).isEqualTo(1)
         verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+        assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
     }
 
     @Test
@@ -188,6 +194,7 @@
         mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
                 .setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
         assertThat(executor.numPending()).isEqualTo(1)
+        assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
     }
 
     @Test
@@ -229,7 +236,7 @@
     }
 
     @Test
-    fun testOnSessionDestroyed_clearsTimeout() {
+    fun testOnSessionDestroyed_active_clearsTimeout() {
         // GIVEN media that is paused
         val mediaPaused = mediaData.copy(isPlaying = false)
         mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaPaused)
@@ -247,7 +254,7 @@
     @Test
     fun testSessionDestroyed_thenRestarts_resetsTimeout() {
         // Assuming we have previously destroyed the session
-        testOnSessionDestroyed_clearsTimeout()
+        testOnSessionDestroyed_active_clearsTimeout()
 
         // WHEN we get an update with media playing
         val playingState = mock(android.media.session.PlaybackState::class.java)
@@ -264,4 +271,91 @@
         }
         verify(timeoutCallback).invoke(eq(KEY), eq(false))
     }
+
+    @Test
+    fun testOnSessionDestroyed_resume_continuesTimeout() {
+        // GIVEN resume media with session info
+        val resumeWithSession = resumeData.copy(token = session.sessionToken)
+        mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeWithSession)
+        verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
+        assertThat(executor.numPending()).isEqualTo(1)
+
+        // WHEN the session is destroyed
+        mediaCallbackCaptor.value.onSessionDestroyed()
+
+        // THEN the controller is unregistered, but the timeout is still scheduled
+        verify(mediaController).unregisterCallback(anyObject())
+        assertThat(executor.numPending()).isEqualTo(1)
+    }
+
+    @Test
+    fun testOnMediaDataLoaded_activeToResume_registersTimeout() {
+        // WHEN a regular media is loaded
+        mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+
+        // AND it turns into a resume control
+        mediaTimeoutListener.onMediaDataLoaded(PACKAGE, KEY, resumeData)
+
+        // THEN we register a timeout
+        assertThat(executor.numPending()).isEqualTo(1)
+        verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+        assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT)
+    }
+
+    @Test
+    fun testOnMediaDataLoaded_pausedToResume_updatesTimeout() {
+        // WHEN regular media is paused
+        val pausedState = PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
+                .build()
+        `when`(mediaController.playbackState).thenReturn(pausedState)
+        mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+        assertThat(executor.numPending()).isEqualTo(1)
+
+        // AND it turns into a resume control
+        mediaTimeoutListener.onMediaDataLoaded(PACKAGE, KEY, resumeData)
+
+        // THEN we update the timeout length
+        assertThat(executor.numPending()).isEqualTo(1)
+        verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+        assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT)
+    }
+
+    @Test
+    fun testOnMediaDataLoaded_resumption_registersTimeout() {
+        // WHEN a resume media is loaded
+        mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData)
+
+        // THEN we register a timeout
+        assertThat(executor.numPending()).isEqualTo(1)
+        verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+        assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT)
+    }
+
+    @Test
+    fun testOnMediaDataLoaded_resumeToActive_updatesTimeout() {
+        // WHEN we have a resume control
+        mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData)
+
+        // AND that media is resumed
+        val playingState = PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
+                .build()
+        `when`(mediaController.playbackState).thenReturn(playingState)
+        mediaTimeoutListener.onMediaDataLoaded(KEY, PACKAGE, mediaData)
+
+        // THEN the timeout length is changed to a regular media control
+        assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
+    }
+
+    @Test
+    fun testOnMediaDataRemoved_resume_timeoutCancelled() {
+        // WHEN we have a resume control
+        testOnMediaDataLoaded_resumption_registersTimeout()
+        // AND the media is removed
+        mediaTimeoutListener.onMediaDataRemoved(PACKAGE)
+
+        // THEN the timeout runnable is cancelled
+        assertThat(executor.numPending()).isEqualTo(0)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
index 03248f7..511848d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
@@ -22,11 +22,13 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.content.pm.UserInfo
+import android.os.Process.SYSTEM_UID
 import android.os.UserHandle
 import android.permission.PermGroupUsage
 import android.permission.PermissionManager
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.plugins.ActivityStarter
@@ -53,6 +55,7 @@
 import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -96,6 +99,8 @@
     private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback>
     @Captor
     private lateinit var intentCaptor: ArgumentCaptor<Intent>
+    @Mock
+    private lateinit var uiEventLogger: UiEventLogger
 
     private val backgroundExecutor = FakeExecutor(FakeSystemClock())
     private val uiExecutor = FakeExecutor(FakeSystemClock())
@@ -136,6 +141,7 @@
                 privacyLogger,
                 keyguardStateController,
                 appOpsController,
+                uiEventLogger,
                 dialogProvider
         )
     }
@@ -550,6 +556,49 @@
         verify(dialog, never()).dismiss()
     }
 
+    @Test
+    fun testCallOnSecondaryUser() {
+        // Calls happen in
+        val usage = createMockPermGroupUsage(uid = SYSTEM_UID, isPhoneCall = true)
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        `when`(userTracker.userProfiles).thenReturn(listOf(
+                UserInfo(ENT_USER_ID, "", 0)
+        ))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialog).show()
+    }
+
+    @Test
+    fun testStartActivityLogs() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, USER_ID)
+        verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+                USER_ID, TEST_PACKAGE_NAME)
+    }
+
+    @Test
+    fun testDismissedDialogLogs() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialog).addOnDismissListener(capture(dialogDismissedCaptor))
+
+        dialogDismissedCaptor.value.onDialogDismissed()
+
+        controller.dismissDialog()
+
+        verify(uiEventLogger, times(1)).log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
+    }
+
     private fun exhaustExecutors() {
         FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
index de7abf8..922c6b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
@@ -14,13 +14,19 @@
 
 package com.android.systemui.qs;
 
-import static com.android.systemui.statusbar.phone.AutoTileManager.INVERSION;
 import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER;
-import static com.android.systemui.statusbar.phone.AutoTileManager.WORK;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.UserHandle;
 import android.provider.Settings.Secure;
 import android.testing.AndroidTestingRunner;
@@ -28,13 +34,24 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.Prefs;
-import com.android.systemui.Prefs.Key;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.concurrent.Executor;
 
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -43,42 +60,38 @@
 
     private static final int USER = 0;
 
+    @Mock
+    private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private QSHost mQSHost;
+    @Mock
+    private DumpManager mDumpManager;
+    @Captor
+    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
+    @Captor
+    private ArgumentCaptor<IntentFilter> mIntentFilterArgumentCaptor;
+
+    private Executor mBackgroundExecutor = Runnable::run; // Direct executor
     private AutoAddTracker mAutoTracker;
+    private SecureSettings mSecureSettings;
 
     @Before
     public void setUp() {
-        Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, "");
-    }
+        MockitoAnnotations.initMocks(this);
 
-    @Test
-    public void testMigration() {
-        Prefs.putBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true);
-        Prefs.putBoolean(mContext, Key.QS_WORK_ADDED, true);
-        mAutoTracker = new AutoAddTracker(mContext, USER);
+        mSecureSettings = new FakeSettings();
+
+        mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, null, USER);
+
+        mAutoTracker = createAutoAddTracker(USER);
         mAutoTracker.initialize();
-
-        assertTrue(mAutoTracker.isAdded(SAVER));
-        assertTrue(mAutoTracker.isAdded(WORK));
-        assertFalse(mAutoTracker.isAdded(INVERSION));
-
-        // These keys have been removed; retrieving their values should always return the default.
-        assertTrue(Prefs.getBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true ));
-        assertFalse(Prefs.getBoolean(mContext, Key.QS_DATA_SAVER_ADDED, false));
-        assertTrue(Prefs.getBoolean(mContext, Key.QS_WORK_ADDED, true));
-        assertFalse(Prefs.getBoolean(mContext, Key.QS_WORK_ADDED, false));
-
-        mAutoTracker.destroy();
     }
 
     @Test
     public void testChangeFromBackup() {
-        mAutoTracker = new AutoAddTracker(mContext, USER);
-        mAutoTracker.initialize();
-
         assertFalse(mAutoTracker.isAdded(SAVER));
 
-        Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, SAVER);
-        mAutoTracker.mObserver.onChange(false);
+        mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, SAVER, USER);
 
         assertTrue(mAutoTracker.isAdded(SAVER));
 
@@ -87,9 +100,6 @@
 
     @Test
     public void testSetAdded() {
-        mAutoTracker = new AutoAddTracker(mContext, USER);
-        mAutoTracker.initialize();
-
         assertFalse(mAutoTracker.isAdded(SAVER));
         mAutoTracker.setTileAdded(SAVER);
 
@@ -100,14 +110,12 @@
 
     @Test
     public void testPersist() {
-        mAutoTracker = new AutoAddTracker(mContext, USER);
-        mAutoTracker.initialize();
-
         assertFalse(mAutoTracker.isAdded(SAVER));
         mAutoTracker.setTileAdded(SAVER);
 
         mAutoTracker.destroy();
-        mAutoTracker = new AutoAddTracker(mContext, USER);
+        mAutoTracker = createAutoAddTracker(USER);
+        mAutoTracker.initialize();
 
         assertTrue(mAutoTracker.isAdded(SAVER));
 
@@ -116,22 +124,158 @@
 
     @Test
     public void testIndependentUsers() {
-        mAutoTracker = new AutoAddTracker(mContext, USER);
-        mAutoTracker.initialize();
         mAutoTracker.setTileAdded(SAVER);
 
-        mAutoTracker = new AutoAddTracker(mContext, USER + 1);
+        mAutoTracker = createAutoAddTracker(USER + 1);
+        mAutoTracker.initialize();
         assertFalse(mAutoTracker.isAdded(SAVER));
     }
 
     @Test
     public void testChangeUser() {
-        mAutoTracker = new AutoAddTracker(mContext, USER);
-        mAutoTracker.initialize();
         mAutoTracker.setTileAdded(SAVER);
 
-        mAutoTracker = new AutoAddTracker(mContext, USER + 1);
+        mAutoTracker = createAutoAddTracker(USER + 1);
         mAutoTracker.changeUser(UserHandle.of(USER));
         assertTrue(mAutoTracker.isAdded(SAVER));
     }
+
+    @Test
+    public void testBroadcastReceiverRegistered() {
+        verify(mBroadcastDispatcher).registerReceiver(
+                any(), mIntentFilterArgumentCaptor.capture(), any(), eq(UserHandle.of(USER)));
+
+        assertTrue(
+                mIntentFilterArgumentCaptor.getValue().hasAction(Intent.ACTION_SETTING_RESTORED));
+    }
+
+    @Test
+    public void testBroadcastReceiverChangesWithUser() {
+        mAutoTracker.changeUser(UserHandle.of(USER + 1));
+
+        InOrder inOrder = Mockito.inOrder(mBroadcastDispatcher);
+        inOrder.verify(mBroadcastDispatcher).unregisterReceiver(any());
+        inOrder.verify(mBroadcastDispatcher)
+                .registerReceiver(any(), any(), any(), eq(UserHandle.of(USER + 1)));
+    }
+
+    @Test
+    public void testSettingRestoredWithTilesNotRemovedInSource_noAutoAddedInTarget() {
+        verify(mBroadcastDispatcher).registerReceiver(
+                mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+        // These tiles were present in the original device
+        String restoredTiles = "saver,work,internet,cast";
+        Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+        // And these tiles have been auto-added in the original device
+        // (no auto-added before restore)
+        String restoredAutoAddTiles = "work";
+        Intent restoreAutoAddTilesIntent =
+                makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles);
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+        // Then, don't remove any current tiles
+        verify(mQSHost, never()).removeTiles(any());
+        assertEquals(restoredAutoAddTiles,
+                mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
+    }
+
+    @Test
+    public void testSettingRestoredWithTilesRemovedInSource_noAutoAddedInTarget() {
+        verify(mBroadcastDispatcher)
+                .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+        // These tiles were present in the original device
+        String restoredTiles = "saver,internet,cast";
+        Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+        // And these tiles have been auto-added in the original device
+        // (no auto-added before restore)
+        String restoredAutoAddTiles = "work";
+        Intent restoreAutoAddTilesIntent =
+                makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles);
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+        // Then, remove work tile
+        verify(mQSHost).removeTiles(List.of("work"));
+        assertEquals(restoredAutoAddTiles,
+                mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
+    }
+
+    @Test
+    public void testSettingRestoredWithTilesRemovedInSource_sameAutoAddedinTarget() {
+        verify(mBroadcastDispatcher)
+                .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+        // These tiles were present in the original device
+        String restoredTiles = "saver,internet,cast";
+        Intent restoreTilesIntent =
+                makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles);
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+        // And these tiles have been auto-added in the original device
+        // (no auto-added before restore)
+        String restoredAutoAddTiles = "work";
+        Intent restoreAutoAddTilesIntent =
+                makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "work", restoredAutoAddTiles);
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+        // Then, remove work tile
+        verify(mQSHost).removeTiles(List.of("work"));
+        assertEquals(restoredAutoAddTiles,
+                mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
+    }
+
+    @Test
+    public void testSettingRestoredWithTilesRemovedInSource_othersAutoAddedinTarget() {
+        verify(mBroadcastDispatcher)
+                .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+        // These tiles were present in the original device
+        String restoredTiles = "saver,internet,cast";
+        Intent restoreTilesIntent =
+                makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles);
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+        // And these tiles have been auto-added in the original device
+        // (no auto-added before restore)
+        String restoredAutoAddTiles = "work";
+        Intent restoreAutoAddTilesIntent =
+                makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "inversion", restoredAutoAddTiles);
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+        // Then, remove work tile
+        verify(mQSHost).removeTiles(List.of("work"));
+
+        String setting = mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER);
+        assertEquals(2, setting.split(",").length);
+        assertTrue(setting.contains("work"));
+        assertTrue(setting.contains("inversion"));
+    }
+
+
+    private Intent makeRestoreIntent(
+            String settingName, String previousValue, String restoredValue) {
+        Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED);
+        intent.putExtra(Intent.EXTRA_SETTING_NAME, settingName);
+        intent.putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue);
+        intent.putExtra(Intent.EXTRA_SETTING_NEW_VALUE, restoredValue);
+        return intent;
+    }
+
+    private AutoAddTracker createAutoAddTracker(int user) {
+        // Null handler wil dispatch sync.
+        return new AutoAddTracker(
+                mSecureSettings,
+                mBroadcastDispatcher,
+                mQSHost,
+                mDumpManager,
+                null,
+                mBackgroundExecutor,
+                user
+        );
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 4cbad5f..0b67c9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -18,14 +18,11 @@
 
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
-import static junit.framework.TestCase.assertFalse;
 
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -68,12 +65,12 @@
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -121,7 +118,6 @@
     private UiEventLogger mUiEventLogger;
     @Mock
     private UserTracker mUserTracker;
-    @Mock
     private SecureSettings mSecureSettings;
     @Mock
     private CustomTileStatePersister mCustomTileStatePersister;
@@ -137,14 +133,16 @@
         MockitoAnnotations.initMocks(this);
         mLooper = TestableLooper.get(this);
         mHandler = new Handler(mLooper.getLooper());
+
+        mSecureSettings = new FakeSettings();
+        mSecureSettings.putStringForUser(
+                QSTileHost.TILES_SETTING, "", "", false, mUserTracker.getUserId(), false);
         mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
                 mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
                 mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker,
                 mSecureSettings, mCustomTileStatePersister, mFeatureFlags);
         setUpTileFactory();
         when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(false);
-        when(mSecureSettings.getStringForUser(eq(QSTileHost.TILES_SETTING), anyInt()))
-                .thenReturn("");
     }
 
     private void setUpTileFactory() {
@@ -416,6 +414,16 @@
                 .removeState(new TileServiceKey(CUSTOM_TILE, mQSTileHost.getUserId()));
     }
 
+    @Test
+    public void testRemoveTiles() {
+        List<String> tiles = List.of("spec1", "spec2", "spec3");
+        mQSTileHost.saveTilesToSettings(tiles);
+
+        mQSTileHost.removeTiles(List.of("spec1", "spec2"));
+
+        assertEquals(List.of("spec3"), mQSTileHost.mTileSpecs);
+    }
+
     private class TestQSTileHost extends QSTileHost {
         TestQSTileHost(Context context, StatusBarIconController iconController,
                 QSFactory defaultFactory, Handler mainHandler, Looper bgLooper,
@@ -442,14 +450,11 @@
         @Override
         void saveTilesToSettings(List<String> tileSpecs) {
             super.saveTilesToSettings(tileSpecs);
-
-            ArgumentCaptor<String> specs = ArgumentCaptor.forClass(String.class);
-            verify(mSecureSettings, atLeastOnce()).putStringForUser(eq(QSTileHost.TILES_SETTING),
-                    specs.capture(), isNull(), eq(false), anyInt(), eq(true));
-
             // After tiles are changed, make sure to call onTuningChanged with the new setting if it
             // changed
-            onTuningChanged(TILES_SETTING, specs.getValue());
+            String specs = mSecureSettings.getStringForUser(
+                    QSTileHost.TILES_SETTING, mUserTracker.getUserId());
+            onTuningChanged(TILES_SETTING, specs);
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 35360bd..8b7e20e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -36,6 +36,8 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusIconContainer
 import com.android.systemui.statusbar.policy.Clock
+import com.android.systemui.statusbar.policy.VariableDateView
+import com.android.systemui.statusbar.policy.VariableDateViewController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
@@ -87,8 +89,14 @@
     @Mock
     private lateinit var privacyDialogController: PrivacyDialogController
     @Mock
+    private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
+    @Mock
+    private lateinit var variableDateViewController: VariableDateViewController
+    @Mock
     private lateinit var clock: Clock
     @Mock
+    private lateinit var variableDateView: VariableDateView
+    @Mock
     private lateinit var mockView: View
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private lateinit var context: Context
@@ -109,6 +117,8 @@
         stubViews()
         `when`(iconContainer.context).thenReturn(context)
         `when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
+        `when`(variableDateViewControllerFactory.create(any()))
+                .thenReturn(variableDateViewController)
         `when`(view.resources).thenReturn(mContext.resources)
         `when`(view.isAttachedToWindow).thenReturn(true)
         `when`(view.context).thenReturn(context)
@@ -133,7 +143,8 @@
                 colorExtractor,
                 privacyDialogController,
                 qsExpansionPathInterpolator,
-                featureFlags
+                featureFlags,
+                variableDateViewControllerFactory
         )
     }
 
@@ -274,6 +285,8 @@
         `when`(view.findViewById<StatusIconContainer>(R.id.statusIcons)).thenReturn(iconContainer)
         `when`(view.findViewById<OngoingPrivacyChip>(R.id.privacy_chip)).thenReturn(privacyChip)
         `when`(view.findViewById<Clock>(R.id.clock)).thenReturn(clock)
+        `when`(view.requireViewById<VariableDateView>(R.id.date)).thenReturn(variableDateView)
+        `when`(view.requireViewById<VariableDateView>(R.id.date_clock)).thenReturn(variableDateView)
     }
 
     private fun setPrivacyController(micCamera: Boolean, location: Boolean) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 63ebe92..23e5168 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -18,6 +18,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -74,6 +75,24 @@
     }
 
     @Test
+    public void testMutateIconDrawable() {
+        SlashImageView iv = mock(SlashImageView.class);
+        Drawable originalDrawable = mock(Drawable.class);
+        Drawable otherDrawable = mock(Drawable.class);
+        State s = new State();
+        s.icon = mock(Icon.class);
+        when(s.icon.getInvisibleDrawable(eq(mContext))).thenReturn(originalDrawable);
+        when(s.icon.getDrawable(eq(mContext))).thenReturn(originalDrawable);
+        when(iv.isShown()).thenReturn(true);
+        when(originalDrawable.getConstantState()).thenReturn(fakeConstantState(otherDrawable));
+
+
+        mIconView.updateIcon(iv, s, /* allowAnimations= */true);
+
+        verify(iv).setState(any(), eq(otherDrawable));
+    }
+
+    @Test
     public void testNoFirstFade() {
         ImageView iv = mock(ImageView.class);
         State s = new State();
@@ -104,4 +123,18 @@
     public void testIconNotSet_toString() {
         assertFalse(mIconView.toString().contains("lastIcon"));
     }
+
+    private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
+        return new Drawable.ConstantState() {
+            @Override
+            public Drawable newDrawable() {
+                return otherDrawable;
+            }
+
+            @Override
+            public int getChangingConfigurations() {
+                return 1;
+            }
+        };
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index d44a526..e939411 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -17,6 +17,7 @@
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertEquals;
 
+import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
@@ -327,4 +328,77 @@
         assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
         assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(connected.name));
     }
+
+    @Test
+    public void testExpandView_wifiNotConnected() {
+        mCastTile.refreshState();
+        mTestableLooper.processAllMessages();
+
+        assertFalse(mCastTile.getState().forceExpandIcon);
+    }
+
+    @Test
+    public void testExpandView_wifiEnabledNotCasting() {
+        enableWifiAndProcessMessages();
+
+        assertTrue(mCastTile.getState().forceExpandIcon);
+    }
+
+    @Test
+    public void testExpandView_casting_projection() {
+        CastController.CastDevice device = new CastController.CastDevice();
+        device.state = CastController.CastDevice.STATE_CONNECTED;
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(device);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        enableWifiAndProcessMessages();
+
+        assertFalse(mCastTile.getState().forceExpandIcon);
+    }
+
+    @Test
+    public void testExpandView_connecting_projection() {
+        CastController.CastDevice connecting = new CastController.CastDevice();
+        connecting.state = CastDevice.STATE_CONNECTING;
+        connecting.name = "Test Casting Device";
+
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(connecting);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        enableWifiAndProcessMessages();
+
+        assertFalse(mCastTile.getState().forceExpandIcon);
+    }
+
+    @Test
+    public void testExpandView_casting_mediaRoute() {
+        CastController.CastDevice device = new CastController.CastDevice();
+        device.state = CastDevice.STATE_CONNECTED;
+        device.tag = mock(MediaRouter.RouteInfo.class);
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(device);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        enableWifiAndProcessMessages();
+
+        assertTrue(mCastTile.getState().forceExpandIcon);
+    }
+
+    @Test
+    public void testExpandView_connecting_mediaRoute() {
+        CastController.CastDevice connecting = new CastController.CastDevice();
+        connecting.state = CastDevice.STATE_CONNECTING;
+        connecting.tag = mock(RouteInfo.class);
+        connecting.name = "Test Casting Device";
+
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(connecting);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        enableWifiAndProcessMessages();
+
+        assertTrue(mCastTile.getState().forceExpandIcon);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
new file mode 100644
index 0000000..77946cf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
@@ -0,0 +1,167 @@
+package com.android.systemui.qs.tiles.dialog;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+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.graphics.drawable.Drawable;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableResources;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.wifitrackerlib.WifiEntry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class InternetAdapterTest extends SysuiTestCase {
+
+    private static final String WIFI_TITLE = "Wi-Fi Title";
+    private static final String WIFI_SUMMARY = "Wi-Fi Summary";
+    private static final int GEAR_ICON_RES_ID = R.drawable.ic_settings_24dp;
+    private static final int LOCK_ICON_RES_ID = R.drawable.ic_friction_lock_closed;
+
+    @Rule
+    public MockitoRule mRule = MockitoJUnit.rule();
+
+    @Mock
+    private WifiEntry mInternetWifiEntry;
+    @Mock
+    private List<WifiEntry> mWifiEntries;
+    @Mock
+    private WifiEntry mWifiEntry;
+    @Mock
+    private InternetDialogController mInternetDialogController;
+    @Mock
+    private WifiUtils.InternetIconInjector mWifiIconInjector;
+    @Mock
+    private Drawable mGearIcon;
+    @Mock
+    private Drawable mLockIcon;
+
+    private TestableResources mTestableResources;
+    private InternetAdapter mInternetAdapter;
+    private InternetAdapter.InternetViewHolder mViewHolder;
+
+    @Before
+    public void setUp() {
+        mTestableResources = mContext.getOrCreateTestableResources();
+        when(mInternetWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+        when(mInternetWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+        when(mInternetWifiEntry.isDefaultNetwork()).thenReturn(true);
+        when(mInternetWifiEntry.hasInternetAccess()).thenReturn(true);
+        when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+        when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+
+        mInternetAdapter = new InternetAdapter(mInternetDialogController);
+        mViewHolder = mInternetAdapter.onCreateViewHolder(new LinearLayout(mContext), 0);
+        mInternetAdapter.setWifiEntries(Arrays.asList(mWifiEntry), 1 /* wifiEntriesCount */);
+        mViewHolder.mWifiIconInjector = mWifiIconInjector;
+    }
+
+    @Test
+    public void getItemCount_returnWifiEntriesCount() {
+        for (int i = 0; i < InternetDialogController.MAX_WIFI_ENTRY_COUNT; i++) {
+            mInternetAdapter.setWifiEntries(mWifiEntries, i /* wifiEntriesCount */);
+
+            assertThat(mInternetAdapter.getItemCount()).isEqualTo(i);
+        }
+    }
+
+    @Test
+    public void onBindViewHolder_bindWithOpenWifiNetwork_verifyView() {
+        when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_NONE);
+        mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mWifiTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mWifiSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mWifiIcon.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void onBindViewHolder_bindWithSecurityWifiNetwork_verifyView() {
+        when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_PSK);
+        mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mWifiTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mWifiSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mWifiIcon.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void onBindViewHolder_wifiLevelUnreachable_shouldNotGetWifiIcon() {
+        reset(mWifiIconInjector);
+        when(mWifiEntry.getLevel()).thenReturn(WifiEntry.WIFI_LEVEL_UNREACHABLE);
+
+        mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+        verify(mWifiIconInjector, never()).getIcon(anyBoolean(), anyInt());
+    }
+
+    @Test
+    public void onBindViewHolder_shouldNotShowXLevelIcon_getIconWithInternet() {
+        when(mWifiEntry.shouldShowXLevelIcon()).thenReturn(false);
+
+        mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+        verify(mWifiIconInjector).getIcon(eq(false) /* noInternet */, anyInt());
+    }
+
+    @Test
+    public void onBindViewHolder_shouldShowXLevelIcon_getIconWithNoInternet() {
+        when(mWifiEntry.shouldShowXLevelIcon()).thenReturn(true);
+
+        mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+        verify(mWifiIconInjector).getIcon(eq(true) /* noInternet */, anyInt());
+    }
+
+    @Test
+    public void viewHolderUpdateEndIcon_wifiConnected_updateGearIcon() {
+        mTestableResources.addOverride(GEAR_ICON_RES_ID, mGearIcon);
+
+        mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_CONNECTED, WifiEntry.SECURITY_PSK);
+
+        assertThat(mViewHolder.mWifiEndIcon.getDrawable()).isEqualTo(mGearIcon);
+    }
+
+    @Test
+    public void viewHolderUpdateEndIcon_wifiDisconnectedAndSecurityPsk_updateLockIcon() {
+        mTestableResources.addOverride(LOCK_ICON_RES_ID, mLockIcon);
+
+        mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_DISCONNECTED, WifiEntry.SECURITY_PSK);
+
+        assertThat(mViewHolder.mWifiEndIcon.getDrawable()).isEqualTo(mLockIcon);
+    }
+
+    @Test
+    public void viewHolderUpdateEndIcon_wifiDisconnectedAndSecurityNone_hideIcon() {
+        mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_DISCONNECTED, WifiEntry.SECURITY_NONE);
+
+        assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.GONE);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
new file mode 100644
index 0000000..baddacc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -0,0 +1,485 @@
+package com.android.systemui.qs.tiles.dialog;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+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.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.text.TextUtils;
+
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.FakeSystemClock;
+import com.android.wifitrackerlib.WifiEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class InternetDialogControllerTest extends SysuiTestCase {
+
+    private static final int SUB_ID = 1;
+    private static final String CONNECTED_TITLE = "Connected Wi-Fi Title";
+    private static final String CONNECTED_SUMMARY = "Connected Wi-Fi Summary";
+
+    @Mock
+    private WifiManager mWifiManager;
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    @Mock
+    private SubscriptionManager mSubscriptionManager;
+    @Mock
+    private Handler mHandler;
+    @Mock
+    private ActivityStarter mActivityStarter;
+    @Mock
+    private GlobalSettings mGlobalSettings;
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
+    private NetworkController.AccessPointController mAccessPointController;
+    @Mock
+    private WifiEntry mConnectedEntry;
+    @Mock
+    private WifiEntry mWifiEntry1;
+    @Mock
+    private WifiEntry mWifiEntry2;
+    @Mock
+    private WifiEntry mWifiEntry3;
+    @Mock
+    private WifiEntry mWifiEntry4;
+    @Mock
+    private ServiceState mServiceState;
+    @Mock
+    private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private WifiUtils.InternetIconInjector mWifiIconInjector;
+    @Mock
+    InternetDialogController.InternetDialogCallback mInternetDialogCallback;
+
+    private MockInternetDialogController mInternetDialogController;
+    private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+    private List<WifiEntry> mAccessPoints = new ArrayList<>();
+    private List<WifiEntry> mWifiEntries = new ArrayList<>();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+        when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+        when(mConnectedEntry.isDefaultNetwork()).thenReturn(true);
+        when(mConnectedEntry.hasInternetAccess()).thenReturn(true);
+        when(mWifiEntry1.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+        when(mWifiEntry2.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+        when(mWifiEntry3.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+        when(mWifiEntry4.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+        mAccessPoints.add(mConnectedEntry);
+        mAccessPoints.add(mWifiEntry1);
+        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+
+        mInternetDialogController = new MockInternetDialogController(mContext,
+                mock(UiEventLogger.class), mock(ActivityStarter.class), mAccessPointController,
+                mSubscriptionManager, mTelephonyManager, mWifiManager,
+                mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher,
+                mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController);
+        mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
+                mInternetDialogController.mOnSubscriptionsChangedListener);
+        mInternetDialogController.onStart(mInternetDialogCallback, true);
+        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+        mInternetDialogController.mActivityStarter = mActivityStarter;
+        mInternetDialogController.mWifiIconInjector = mWifiIconInjector;
+    }
+
+    @Test
+    public void getDialogTitleText_withAirplaneModeOn_returnAirplaneMode() {
+        mInternetDialogController.setAirplaneModeEnabled(true);
+
+        assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
+                getResourcesString("airplane_mode")));
+    }
+
+    @Test
+    public void getDialogTitleText_withAirplaneModeOff_returnInternet() {
+        mInternetDialogController.setAirplaneModeEnabled(false);
+
+        assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
+                getResourcesString("quick_settings_internet_label")));
+    }
+
+    @Test
+    public void getSubtitleText_withAirplaneModeOn_returnNull() {
+        mInternetDialogController.setAirplaneModeEnabled(true);
+
+        assertThat(mInternetDialogController.getSubtitleText(false)).isNull();
+    }
+
+    @Test
+    public void getSubtitleText_withWifiOff_returnWifiIsOff() {
+        mInternetDialogController.setAirplaneModeEnabled(false);
+        when(mWifiManager.isWifiEnabled()).thenReturn(false);
+
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isEqualTo(getResourcesString("wifi_is_off"));
+
+        // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+        mInternetDialogController.mCanConfigWifi = false;
+
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isNotEqualTo(getResourcesString("wifi_is_off"));
+    }
+
+    @Test
+    public void getSubtitleText_withNoWifiEntry_returnSearchWifi() {
+        mInternetDialogController.setAirplaneModeEnabled(false);
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+        assertThat(mInternetDialogController.getSubtitleText(true))
+                .isEqualTo(getResourcesString("wifi_empty_list_wifi_on"));
+
+        // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+        mInternetDialogController.mCanConfigWifi = false;
+
+        assertThat(mInternetDialogController.getSubtitleText(true))
+                .isNotEqualTo(getResourcesString("wifi_empty_list_wifi_on"));
+    }
+
+    @Test
+    public void getSubtitleText_withWifiEntry_returnTapToConnect() {
+        // The preconditions WiFi Entries is already in setUp()
+        mInternetDialogController.setAirplaneModeEnabled(false);
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isEqualTo(getResourcesString("tap_a_network_to_connect"));
+
+        // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+        mInternetDialogController.mCanConfigWifi = false;
+
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isNotEqualTo(getResourcesString("tap_a_network_to_connect"));
+    }
+
+    @Test
+    public void getSubtitleText_deviceLockedWithWifiOn_returnUnlockToViewNetworks() {
+        mInternetDialogController.setAirplaneModeEnabled(false);
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+        assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
+                getResourcesString("unlock_to_view_networks")));
+    }
+
+    @Test
+    public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
+        mInternetDialogController.setAirplaneModeEnabled(false);
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState();
+        doReturn(mServiceState).when(mTelephonyManager).getServiceState();
+        doReturn(TelephonyManager.DATA_DISCONNECTED).when(mTelephonyManager).getDataState();
+
+        assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
+                getResourcesString("all_network_unavailable")));
+    }
+
+    @Test
+    public void getSubtitleText_withMobileDataDisabled_returnNoOtherAvailable() {
+        mInternetDialogController.setAirplaneModeEnabled(false);
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+        doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState();
+        doReturn(mServiceState).when(mTelephonyManager).getServiceState();
+
+        when(mTelephonyManager.isDataEnabled()).thenReturn(false);
+
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isEqualTo(getResourcesString("non_carrier_network_unavailable"));
+
+        // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+        mInternetDialogController.mCanConfigWifi = false;
+
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isNotEqualTo(getResourcesString("non_carrier_network_unavailable"));
+    }
+
+    @Test
+    public void getWifiDetailsSettingsIntent_withNoKey_returnNull() {
+        assertThat(mInternetDialogController.getWifiDetailsSettingsIntent(null)).isNull();
+    }
+
+    @Test
+    public void getWifiDetailsSettingsIntent_withKey_returnIntent() {
+        assertThat(mInternetDialogController.getWifiDetailsSettingsIntent("test_key")).isNotNull();
+    }
+
+    @Test
+    public void getInternetWifiDrawable_withConnectedEntry_returnIntentIconWithCorrectColor() {
+        final Drawable drawable = mock(Drawable.class);
+        when(mWifiIconInjector.getIcon(anyBoolean(), anyInt())).thenReturn(drawable);
+
+        mInternetDialogController.getInternetWifiDrawable(mConnectedEntry);
+
+        verify(mWifiIconInjector).getIcon(eq(false), anyInt());
+        verify(drawable).setTint(mContext.getColor(R.color.connected_network_primary_color));
+    }
+
+    @Test
+    public void getInternetWifiDrawable_withWifiLevelUnreachable_returnNull() {
+        when(mConnectedEntry.getLevel()).thenReturn(WifiEntry.WIFI_LEVEL_UNREACHABLE);
+
+        Drawable drawable = mInternetDialogController.getInternetWifiDrawable(mConnectedEntry);
+
+        assertThat(drawable).isNull();
+    }
+
+    @Test
+    public void launchWifiNetworkDetailsSetting_withNoWifiEntryKey_doNothing() {
+        mInternetDialogController.launchWifiNetworkDetailsSetting(null /* key */);
+
+        verify(mActivityStarter, never())
+                .postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
+    }
+
+    @Test
+    public void launchWifiNetworkDetailsSetting_withWifiEntryKey_startActivity() {
+        mInternetDialogController.launchWifiNetworkDetailsSetting("wifi_entry_key");
+
+        verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
+    }
+
+    @Test
+    public void isDeviceLocked_keyguardIsUnlocked_returnFalse() {
+        when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+
+        assertThat(mInternetDialogController.isDeviceLocked()).isFalse();
+    }
+
+    @Test
+    public void isDeviceLocked_keyguardIsLocked_returnTrue() {
+        when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+        assertThat(mInternetDialogController.isDeviceLocked()).isTrue();
+    }
+
+    @Test
+    public void onAccessPointsChanged_canNotConfigWifi_doNothing() {
+        reset(mInternetDialogCallback);
+        mInternetDialogController.mCanConfigWifi = false;
+
+        mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+        verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any());
+    }
+
+    @Test
+    public void onAccessPointsChanged_nullAccessPoints_callbackBothNull() {
+        reset(mInternetDialogCallback);
+
+        mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+        verify(mInternetDialogCallback)
+                .onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */);
+    }
+
+    @Test
+    public void onAccessPointsChanged_oneConnectedEntry_callbackConnectedEntryOnly() {
+        reset(mInternetDialogCallback);
+        mInternetDialogController.setAirplaneModeEnabled(true);
+        mAccessPoints.clear();
+        mAccessPoints.add(mConnectedEntry);
+
+        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+        mWifiEntries.clear();
+        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+    }
+
+    @Test
+    public void onAccessPointsChanged_noConnectedEntryAndOneOther_callbackWifiEntriesOnly() {
+        reset(mInternetDialogCallback);
+        mInternetDialogController.setAirplaneModeEnabled(true);
+        mAccessPoints.clear();
+        mAccessPoints.add(mWifiEntry1);
+
+        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+        mWifiEntries.clear();
+        mWifiEntries.add(mWifiEntry1);
+        verify(mInternetDialogCallback)
+                .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+    }
+
+    @Test
+    public void onAccessPointsChanged_oneConnectedEntryAndOneOther_callbackCorrectly() {
+        reset(mInternetDialogCallback);
+        mInternetDialogController.setAirplaneModeEnabled(true);
+        mAccessPoints.clear();
+        mAccessPoints.add(mConnectedEntry);
+        mAccessPoints.add(mWifiEntry1);
+
+        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+        mWifiEntries.clear();
+        mWifiEntries.add(mWifiEntry1);
+        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+    }
+
+    @Test
+    public void onAccessPointsChanged_oneConnectedEntryAndTwoOthers_callbackCorrectly() {
+        reset(mInternetDialogCallback);
+        mInternetDialogController.setAirplaneModeEnabled(true);
+        mAccessPoints.clear();
+        mAccessPoints.add(mConnectedEntry);
+        mAccessPoints.add(mWifiEntry1);
+        mAccessPoints.add(mWifiEntry2);
+
+        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+        mWifiEntries.clear();
+        mWifiEntries.add(mWifiEntry1);
+        mWifiEntries.add(mWifiEntry2);
+        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+    }
+
+    @Test
+    public void onAccessPointsChanged_oneConnectedEntryAndThreeOthers_callbackCutMore() {
+        reset(mInternetDialogCallback);
+        mInternetDialogController.setAirplaneModeEnabled(true);
+        mAccessPoints.clear();
+        mAccessPoints.add(mConnectedEntry);
+        mAccessPoints.add(mWifiEntry1);
+        mAccessPoints.add(mWifiEntry2);
+        mAccessPoints.add(mWifiEntry3);
+
+        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+        mWifiEntries.clear();
+        mWifiEntries.add(mWifiEntry1);
+        mWifiEntries.add(mWifiEntry2);
+        mWifiEntries.add(mWifiEntry3);
+        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+
+        // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
+        reset(mInternetDialogCallback);
+        mInternetDialogController.setAirplaneModeEnabled(false);
+
+        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+        mWifiEntries.remove(mWifiEntry3);
+        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+    }
+
+    @Test
+    public void onAccessPointsChanged_oneConnectedEntryAndFourOthers_callbackCutMore() {
+        reset(mInternetDialogCallback);
+        mInternetDialogController.setAirplaneModeEnabled(true);
+        mAccessPoints.clear();
+        mAccessPoints.add(mConnectedEntry);
+        mAccessPoints.add(mWifiEntry1);
+        mAccessPoints.add(mWifiEntry2);
+        mAccessPoints.add(mWifiEntry3);
+        mAccessPoints.add(mWifiEntry4);
+
+        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+        mWifiEntries.clear();
+        mWifiEntries.add(mWifiEntry1);
+        mWifiEntries.add(mWifiEntry2);
+        mWifiEntries.add(mWifiEntry3);
+        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+
+        // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
+        reset(mInternetDialogCallback);
+        mInternetDialogController.setAirplaneModeEnabled(false);
+
+        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+        mWifiEntries.remove(mWifiEntry3);
+        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+    }
+
+    private String getResourcesString(String name) {
+        return mContext.getResources().getString(getResourcesId(name));
+    }
+
+    private int getResourcesId(String name) {
+        return mContext.getResources().getIdentifier(name, "string",
+                mContext.getPackageName());
+    }
+
+    private class MockInternetDialogController extends InternetDialogController {
+
+        private GlobalSettings mGlobalSettings;
+        private boolean mIsAirplaneModeOn;
+
+        MockInternetDialogController(Context context, UiEventLogger uiEventLogger,
+                ActivityStarter starter, AccessPointController accessPointController,
+                SubscriptionManager subscriptionManager, TelephonyManager telephonyManager,
+                @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
+                @Main Handler handler, @Main Executor mainExecutor,
+                BroadcastDispatcher broadcastDispatcher,
+                KeyguardUpdateMonitor keyguardUpdateMonitor, GlobalSettings globalSettings,
+                KeyguardStateController keyguardStateController) {
+            super(context, uiEventLogger, starter, accessPointController, subscriptionManager,
+                    telephonyManager, wifiManager, connectivityManager, handler, mainExecutor,
+                    broadcastDispatcher, keyguardUpdateMonitor, globalSettings,
+                    keyguardStateController);
+            mGlobalSettings = globalSettings;
+        }
+
+        @Override
+        boolean isAirplaneModeEnabled() {
+            return mIsAirplaneModeOn;
+        }
+
+        public void setAirplaneModeEnabled(boolean enabled) {
+            mIsAirplaneModeOn = enabled;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
new file mode 100644
index 0000000..fa9c053
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -0,0 +1,292 @@
+package com.android.systemui.qs.tiles.dialog;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.wifitrackerlib.WifiEntry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class InternetDialogTest extends SysuiTestCase {
+
+    private static final String MOBILE_NETWORK_TITLE = "Mobile Title";
+    private static final String MOBILE_NETWORK_SUMMARY = "Mobile Summary";
+    private static final String WIFI_TITLE = "Connected Wi-Fi Title";
+    private static final String WIFI_SUMMARY = "Connected Wi-Fi Summary";
+
+    @Mock
+    private Handler mHandler;
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    @Mock
+    private WifiManager mWifiManager;
+    @Mock
+    private WifiEntry mInternetWifiEntry;
+    @Mock
+    private List<WifiEntry> mWifiEntries;
+    @Mock
+    private InternetAdapter mInternetAdapter;
+    @Mock
+    private InternetDialogController mInternetDialogController;
+
+    private InternetDialog mInternetDialog;
+    private View mDialogView;
+    private View mSubTitle;
+    private LinearLayout mMobileDataToggle;
+    private LinearLayout mWifiToggle;
+    private LinearLayout mConnectedWifi;
+    private RecyclerView mWifiList;
+    private LinearLayout mSeeAll;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        when(mInternetWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+        when(mInternetWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+        when(mInternetWifiEntry.isDefaultNetwork()).thenReturn(true);
+        when(mInternetWifiEntry.hasInternetAccess()).thenReturn(true);
+        when(mWifiEntries.size()).thenReturn(1);
+
+        when(mInternetDialogController.getMobileNetworkTitle()).thenReturn(MOBILE_NETWORK_TITLE);
+        when(mInternetDialogController.getMobileNetworkSummary())
+                .thenReturn(MOBILE_NETWORK_SUMMARY);
+        when(mInternetDialogController.getWifiManager()).thenReturn(mWifiManager);
+
+        mInternetDialog = new InternetDialog(mContext, mock(InternetDialogFactory.class),
+                mInternetDialogController, true, true, true, mock(UiEventLogger.class), mHandler);
+        mInternetDialog.mAdapter = mInternetAdapter;
+        mInternetDialog.onAccessPointsChanged(mWifiEntries, mInternetWifiEntry);
+        mInternetDialog.show();
+
+        mDialogView = mInternetDialog.mDialogView;
+        mSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
+        mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_network_layout);
+        mWifiToggle = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
+        mConnectedWifi = mDialogView.requireViewById(R.id.wifi_connected_layout);
+        mWifiList = mDialogView.requireViewById(R.id.wifi_list_layout);
+        mSeeAll = mDialogView.requireViewById(R.id.see_all_layout);
+    }
+
+    @After
+    public void tearDown() {
+        mInternetDialog.dismissDialog();
+    }
+
+    @Test
+    public void hideWifiViews_WifiViewsGone() {
+        mInternetDialog.hideWifiViews();
+
+        assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+        assertThat(mWifiToggle.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void updateDialog_withApmOn_internetDialogSubTitleGone() {
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mSubTitle.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void updateDialog_withApmOff_internetDialogSubTitleVisible() {
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void updateDialog_withApmOn_mobileDataLayoutGone() {
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
+        // The preconditions WiFi ON and Internet WiFi are already in setUp()
+        doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void updateDialog_wifiOnAndNoConnectedWifi_hideConnectedWifi() {
+        // The precondition WiFi ON is already in setUp()
+        mInternetDialog.onAccessPointsChanged(mWifiEntries, null /* connectedEntry*/);
+        doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void updateDialog_wifiOnAndNoWifiList_hideWifiListAndSeeAll() {
+        // The precondition WiFi ON is already in setUp()
+        mInternetDialog.onAccessPointsChanged(null /* wifiEntries */, mInternetWifiEntry);
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void updateDialog_wifiOnAndHasWifiList_showWifiListAndSeeAll() {
+        // The preconditions WiFi ON and WiFi entries are already in setUp()
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void updateDialog_deviceLockedAndHasInternetWifi_showHighlightWifiToggle() {
+        // The preconditions WiFi ON and Internet WiFi are already in setUp()
+        when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mWifiToggle.getBackground()).isNotNull();
+    }
+
+    @Test
+    public void updateDialog_deviceLockedAndHasInternetWifi_hideConnectedWifi() {
+        // The preconditions WiFi ON and Internet WiFi are already in setUp()
+        when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void updateDialog_deviceLockedAndHasWifiList_hideWifiListAndSeeAll() {
+        // The preconditions WiFi entries are already in setUp()
+        when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+        mInternetDialog.updateDialog();
+
+        assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void onClickSeeMoreButton_clickSeeAll_verifyLaunchNetworkSetting() {
+        mSeeAll.performClick();
+
+        verify(mInternetDialogController).launchNetworkSetting();
+    }
+
+    @Test
+    public void showProgressBar_wifiDisabled_hideProgressBar() {
+        Mockito.reset(mHandler);
+        when(mWifiManager.isWifiEnabled()).thenReturn(false);
+
+        mInternetDialog.showProgressBar();
+
+        assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+        verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong());
+    }
+
+    @Test
+    public void showProgressBar_deviceLocked_hideProgressBar() {
+        Mockito.reset(mHandler);
+        when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+        mInternetDialog.showProgressBar();
+
+        assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+        verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong());
+    }
+
+    @Test
+    public void showProgressBar_wifiEnabledWithWifiEntry_showProgressBarThenHide() {
+        Mockito.reset(mHandler);
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+
+        mInternetDialog.showProgressBar();
+
+        // Show progress bar
+        assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
+
+        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mHandler).postDelayed(runnableCaptor.capture(),
+                eq(InternetDialog.PROGRESS_DELAY_MS));
+        runnableCaptor.getValue().run();
+
+        // Then hide progress bar
+        assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+    }
+
+    @Test
+    public void showProgressBar_wifiEnabledWithoutWifiEntries_showProgressBarThenHideSearch() {
+        Mockito.reset(mHandler);
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        mInternetDialog.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry*/);
+
+        mInternetDialog.showProgressBar();
+
+        // Show progress bar
+        assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
+
+        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mHandler).postDelayed(runnableCaptor.capture(),
+                eq(InternetDialog.PROGRESS_DELAY_MS));
+        runnableCaptor.getValue().run();
+
+        // Then hide searching sub-title only
+        assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
+        assertThat(mInternetDialog.mIsSearchingHidden).isTrue();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
index 10c878a..6f081c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
@@ -34,6 +34,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.screenshot.ScrollCaptureClient.Session;
 
@@ -274,7 +275,8 @@
             when(client.start(/* response */ any(), /* maxPages */ anyFloat()))
                     .thenReturn(immediateFuture(session));
             return new ScrollCaptureController(context, context.getMainExecutor(),
-                    client, new ImageTileSet(context.getMainThreadHandler()));
+                    client, new ImageTileSet(context.getMainThreadHandler()),
+                    new UiEventLoggerFake());
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 21c6292..f3762c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -423,17 +423,18 @@
         final boolean credentialAllowed = true;
         final boolean requireConfirmation = true;
         final int userId = 10;
-        final String packageName = "test";
         final long operationId = 1;
+        final String packageName = "test";
+        final long requestId = 10;
         final int multiSensorConfig = BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
 
         mCommandQueue.showAuthenticationDialog(promptInfo, receiver, sensorIds,
-                credentialAllowed, requireConfirmation , userId, packageName, operationId,
+                credentialAllowed, requireConfirmation, userId, operationId, packageName, requestId,
                 multiSensorConfig);
         waitForIdleSync();
         verify(mCallbacks).showAuthenticationDialog(eq(promptInfo), eq(receiver), eq(sensorIds),
-                eq(credentialAllowed), eq(requireConfirmation), eq(userId), eq(packageName),
-                eq(operationId), eq(multiSensorConfig));
+                eq(credentialAllowed), eq(requireConfirmation), eq(userId), eq(operationId),
+                eq(packageName), eq(requestId), eq(multiSensorConfig));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index f5ce673..09a3d35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -111,6 +111,7 @@
     private static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName("com.android.foo",
             "bar");
 
+    private String mKeyguardTryFingerprintMsg;
     private String mDisclosureWithOrganization;
     private String mDisclosureGeneric;
     private String mFinancedDisclosureWithOrganization;
@@ -182,6 +183,7 @@
         mContext.addMockSystemService(UserManager.class, mUserManager);
         mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
         mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
+        mKeyguardTryFingerprintMsg = mContext.getString(R.string.keyguard_try_fingerprint);
         mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name,
                 ORGANIZATION_NAME);
         mDisclosureGeneric = mContext.getString(R.string.do_disclosure_generic);
@@ -637,7 +639,7 @@
     }
 
     @Test
-    public void onRefreshBatteryInfo_fullChargedWithOverheat_presentCharged() {
+    public void onRefreshBatteryInfo_fullChargedWithOverheat_presentChargingLimited() {
         createController();
         BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
                 100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
@@ -649,6 +651,24 @@
 
         verifyIndicationMessage(
                 INDICATION_TYPE_BATTERY,
+                mContext.getString(
+                        R.string.keyguard_plugged_in_charging_limited,
+                        NumberFormat.getPercentInstance().format(100 / 100f)));
+    }
+
+    @Test
+    public void onRefreshBatteryInfo_fullChargedWithoutOverheat_presentCharged() {
+        createController();
+        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
+                100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
+                BatteryManager.BATTERY_HEALTH_GOOD, 0 /* maxChargingWattage */,
+                true /* present */);
+
+        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
+        mController.setVisible(true);
+
+        verifyIndicationMessage(
+                INDICATION_TYPE_BATTERY,
                 mContext.getString(R.string.keyguard_charged));
     }
 
@@ -677,6 +697,34 @@
         verifyTransientMessage(message);
     }
 
+    @Test
+    public void faceAuthMessageSuppressed() {
+        createController();
+        String faceHelpMsg = "Face auth help message";
+
+        // GIVEN state of showing message when keyguard screen is on
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isScreenOn()).thenReturn(true);
+
+        // GIVEN fingerprint is also running (not udfps)
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isUdfpsAvailable()).thenReturn(false);
+
+        mController.setVisible(true);
+
+        // WHEN a face help message comes in
+        mController.getKeyguardCallback().onBiometricHelp(
+                KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, faceHelpMsg,
+                BiometricSourceType.FACE);
+
+        // THEN "try fingerprint" message appears (and not the face help message)
+        verifyTransientMessage(mKeyguardTryFingerprintMsg);
+
+        // THEN the face help message is still announced for a11y
+        verify(mIndicationAreaBottom).announceForAccessibility(eq(faceHelpMsg));
+    }
+
     private void sendUpdateDisclosureBroadcast() {
         mBroadcastReceiver.onReceive(mContext, new Intent());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
new file mode 100644
index 0000000..97fe25d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
@@ -0,0 +1,66 @@
+/*
+ * 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
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.function.Consumer
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LightRevealScrimTest : SysuiTestCase() {
+
+  private lateinit var scrim: LightRevealScrim
+  private var isOpaque = false
+
+  @Before
+  fun setUp() {
+    scrim = LightRevealScrim(context, null)
+    scrim.isScrimOpaqueChangedListener = Consumer { opaque ->
+      isOpaque = opaque
+    }
+    scrim.revealAmount = 0f
+    assertTrue("Scrim is not opaque in initial setup", scrim.isScrimOpaque)
+  }
+
+  @Test
+  fun testAlphaSetsOpaque() {
+    scrim.alpha = 0.5f
+    assertFalse("Scrim is opaque even though alpha is set", scrim.isScrimOpaque)
+  }
+
+  @Test
+  fun testVisibilitySetsOpaque() {
+    scrim.visibility = View.INVISIBLE
+    assertFalse("Scrim is opaque even though it's invisible", scrim.isScrimOpaque)
+    scrim.visibility = View.GONE
+    assertFalse("Scrim is opaque even though it's gone", scrim.isScrimOpaque)
+  }
+
+  @Test
+  fun testRevealSetsOpaque() {
+    scrim.revealAmount = 0.5f
+    assertFalse("Scrim is opaque even though it's revealed", scrim.isScrimOpaque)
+  }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index a7b1446..465370b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -25,6 +25,7 @@
 import android.view.ViewRootImpl
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.phone.BiometricUnlockController
@@ -32,6 +33,7 @@
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -167,6 +169,22 @@
     }
 
     @Test
+    fun onPanelExpansionChanged_respectsMinPanelPullDownFraction() {
+        notificationShadeDepthController.panelPullDownMinFraction = 0.5f
+        notificationShadeDepthController.onPanelExpansionChanged(0.5f /* expansion */,
+                true /* tracking */)
+        assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0f)
+
+        notificationShadeDepthController.onPanelExpansionChanged(0.75f /* expansion */,
+                true /* tracking */)
+        assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0.5f)
+
+        notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */,
+                true /* tracking */)
+        assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(1f)
+    }
+
+    @Test
     fun onStateChanged_reevalutesBlurs_ifSameRadiusAndNewState() {
         onPanelExpansionChanged_apliesBlur_ifShade()
         clearInvocations(choreographer)
@@ -178,10 +196,21 @@
 
     @Test
     fun setQsPanelExpansion_appliesBlur() {
+        statusBarState = StatusBarState.KEYGUARD
         notificationShadeDepthController.qsPanelExpansion = 1f
-        notificationShadeDepthController.onPanelExpansionChanged(0.5f, tracking = false)
+        notificationShadeDepthController.onPanelExpansionChanged(1f, tracking = false)
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
-        verify(blurUtils).applyBlur(any(), anyInt(), eq(false))
+        verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
+    }
+
+    @Test
+    fun setQsPanelExpansion_easing() {
+        statusBarState = StatusBarState.KEYGUARD
+        notificationShadeDepthController.qsPanelExpansion = 0.25f
+        notificationShadeDepthController.onPanelExpansionChanged(1f, tracking = false)
+        notificationShadeDepthController.updateBlurCallback.doFrame(0)
+        verify(wallpaperManager).setWallpaperZoomOut(any(),
+                eq(Interpolators.getNotificationScrimAlpha(0.25f, false /* notifications */)))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 116f807..efe6a31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -44,6 +44,8 @@
 import com.android.systemui.statusbar.FeatureFlags
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
 import com.android.systemui.util.concurrency.FakeExecution
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -90,6 +92,8 @@
     @Mock
     private lateinit var statusBarStateController: StatusBarStateController
     @Mock
+    private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock
     private lateinit var handler: Handler
 
     @Mock
@@ -107,12 +111,15 @@
     private lateinit var configChangeListenerCaptor: ArgumentCaptor<ConfigurationListener>
     @Captor
     private lateinit var statusBarStateListenerCaptor: ArgumentCaptor<StateListener>
+    @Captor
+    private lateinit var deviceProvisionedCaptor: ArgumentCaptor<DeviceProvisionedListener>
 
     private lateinit var sessionListener: OnTargetsAvailableListener
     private lateinit var userListener: UserTracker.Callback
     private lateinit var settingsObserver: ContentObserver
     private lateinit var configChangeListener: ConfigurationListener
     private lateinit var statusBarStateListener: StateListener
+    private lateinit var deviceProvisionedListener: DeviceProvisionedListener
 
     private val clock = FakeSystemClock()
     private val executor = FakeExecutor(clock)
@@ -144,6 +151,8 @@
         `when`(plugin.getView(any())).thenReturn(fakeSmartspaceView)
         `when`(userTracker.userProfiles).thenReturn(userList)
         `when`(statusBarStateController.dozeAmount).thenReturn(0.5f)
+        `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+        `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
 
         setActiveUser(userHandlePrimary)
         setAllowPrivateNotifications(userHandlePrimary, true)
@@ -161,11 +170,15 @@
                 contentResolver,
                 configurationController,
                 statusBarStateController,
+                deviceProvisionedController,
                 execution,
                 executor,
                 handler,
                 Optional.of(plugin)
                 )
+
+        verify(deviceProvisionedController).addCallback(capture(deviceProvisionedCaptor))
+        deviceProvisionedListener = deviceProvisionedCaptor.value
     }
 
     @Test(expected = RuntimeException::class)
@@ -180,6 +193,27 @@
     }
 
     @Test
+    fun connectOnlyAfterDeviceIsProvisioned() {
+        // GIVEN an unprovisioned device and an attempt to connect
+        `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(false)
+        `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(false)
+
+        // WHEN a connection attempt is made
+        controller.buildAndConnectView(fakeParent)
+
+        // THEN no session is created
+        verify(smartspaceManager, never()).createSmartspaceSession(any())
+
+        // WHEN it does become provisioned
+        `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+        `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
+        deviceProvisionedListener.onUserSetupChanged()
+
+        // THEN the session is created
+        verify(smartspaceManager).createSmartspaceSession(any())
+    }
+
+    @Test
     fun testListenersAreRegistered() {
         // GIVEN a listener is added after a session is created
         connectSession()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index d7661be..f247788 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -141,7 +141,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class NotificationPanelViewTest extends SysuiTestCase {
+public class NotificationPanelViewControllerTest extends SysuiTestCase {
 
     private static final int NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE = 50;
 
@@ -470,6 +470,12 @@
     }
 
     @Test
+    public void testSetMinFraction() {
+        mNotificationPanelViewController.setMinFraction(0.5f);
+        verify(mNotificationShadeDepthController).setPanelPullDownMinFraction(eq(0.5f));
+    }
+
+    @Test
     public void testSetDozing_notifiesNsslAndStateController() {
         mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */,
                 null /* touch */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index ddd7854..90b8a74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -146,7 +146,7 @@
         mNotificationShadeWindowController.attach();
 
         clearInvocations(mWindowManager);
-        mNotificationShadeWindowController.setLightRevealScrimAmount(0f);
+        mNotificationShadeWindowController.setLightRevealScrimOpaque(true);
         verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
         assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) == 0).isTrue();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index 6c1a3c9..bcc257d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -16,6 +16,11 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -55,6 +60,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -91,6 +98,10 @@
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
 
+    @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
+            mInteractionEventHandlerCaptor;
+    private NotificationShadeWindowView.InteractionEventHandler mInteractionEventHandler;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -147,4 +158,49 @@
         verify(mDragDownHelper).onTouchEvent(ev);
         ev.recycle();
     }
+
+    @Test
+    public void testInterceptTouchWhenShowingAltAuth() {
+        captureInteractionEventHandler();
+
+        // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+        when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+        when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
+
+        // THEN we should intercept touch
+        assertTrue(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class)));
+    }
+
+    @Test
+    public void testNoInterceptTouch() {
+        captureInteractionEventHandler();
+
+        // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+        when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(false);
+        when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
+
+        // THEN we shouldn't intercept touch
+        assertFalse(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class)));
+    }
+
+    @Test
+    public void testHandleTouchEventWhenShowingAltAuth() {
+        captureInteractionEventHandler();
+
+        // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+        when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+        when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
+
+        // THEN we should handle the touch
+        assertTrue(mInteractionEventHandler.handleTouchEvent(mock(MotionEvent.class)));
+    }
+
+    private void captureInteractionEventHandler() {
+        verify(mView).setInteractionEventHandler(mInteractionEventHandlerCaptor.capture());
+        mInteractionEventHandler = mInteractionEventHandlerCaptor.getValue();
+
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 6de5866..47c8806 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -624,7 +624,7 @@
 
         assertScrimTinted(Map.of(
                 mScrimInFront, false,
-                mScrimBehind, false,
+                mScrimBehind, true,
                 mScrimForBubble, true
         ));
 
@@ -705,7 +705,7 @@
     public void qsExpansion_half_clippingQs() {
         reset(mScrimBehind);
         mScrimController.setClipsQsScrim(true);
-        mScrimController.setQsPosition(0.5f, 999 /* value doesn't matter */);
+        mScrimController.setQsPosition(0.25f, 999 /* value doesn't matter */);
         finishAnimationsImmediately();
 
         assertScrimAlpha(Map.of(
@@ -1136,7 +1136,7 @@
     @Test
     public void testScrimsVisible_whenShadeVisibleOnLockscreen() {
         mScrimController.transitionTo(ScrimState.KEYGUARD);
-        mScrimController.setQsPosition(0.5f, 300);
+        mScrimController.setQsPosition(0.25f, 300);
 
         assertScrimAlpha(Map.of(
                 mScrimBehind, SEMI_TRANSPARENT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 57198db..4a5770d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -238,7 +238,7 @@
         mNetworkController.setNoNetworksAvailable(false);
         setWifiStateForVcn(true, testSsid);
         setWifiLevelForVcn(0);
-        verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][0]);
+        verifyLastMobileDataIndicatorsForVcn(true, 0, TelephonyIcons.ICON_CWF, false);
 
         mNetworkController.setNoNetworksAvailable(true);
         for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
@@ -246,11 +246,11 @@
 
             setConnectivityViaCallbackInNetworkControllerForVcn(
                     NetworkCapabilities.TRANSPORT_CELLULAR, true, true, mVcnTransportInfo);
-            verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
+            verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, true);
 
             setConnectivityViaCallbackInNetworkControllerForVcn(
                     NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
-            verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
+            verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, false);
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
new file mode 100644
index 0000000..871a48c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2021 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.policy
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Date
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class VariableDateViewControllerTest : SysuiTestCase() {
+
+    companion object {
+        private const val TIME_STAMP = 1_500_000_000_000
+        private const val LONG_PATTERN = "EEEMMMd"
+        private const val SHORT_PATTERN = "MMMd"
+        private const val CHAR_WIDTH = 10f
+    }
+
+    @Mock
+    private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock
+    private lateinit var view: VariableDateView
+    @Captor
+    private lateinit var onMeasureListenerCaptor: ArgumentCaptor<VariableDateView.OnMeasureListener>
+
+    private var lastText: String? = null
+
+    private lateinit var systemClock: FakeSystemClock
+    private lateinit var testableLooper: TestableLooper
+    private lateinit var testableHandler: Handler
+    private lateinit var controller: VariableDateViewController
+
+    private lateinit var longText: String
+    private lateinit var shortText: String
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+        testableHandler = Handler(testableLooper.looper)
+
+        systemClock = FakeSystemClock()
+        systemClock.setCurrentTimeMillis(TIME_STAMP)
+
+        `when`(view.longerPattern).thenReturn(LONG_PATTERN)
+        `when`(view.shorterPattern).thenReturn(SHORT_PATTERN)
+        `when`(view.handler).thenReturn(testableHandler)
+
+        `when`(view.setText(anyString())).thenAnswer {
+            lastText = it.arguments[0] as? String
+            Unit
+        }
+        `when`(view.isAttachedToWindow).thenReturn(true)
+
+        val date = Date(TIME_STAMP)
+        longText = getTextForFormat(date, getFormatFromPattern(LONG_PATTERN))
+        shortText = getTextForFormat(date, getFormatFromPattern(SHORT_PATTERN))
+
+        // Assume some sizes for the text, the controller doesn't need to know if these sizes are
+        // the true ones
+        `when`(view.getDesiredWidthForText(any())).thenAnswer {
+            getTextLength(it.arguments[0] as CharSequence)
+        }
+
+        controller = VariableDateViewController(
+                systemClock,
+                broadcastDispatcher,
+                testableHandler,
+                view
+        )
+
+        controller.init()
+        testableLooper.processAllMessages()
+
+        verify(view).onAttach(capture(onMeasureListenerCaptor))
+    }
+
+    @Test
+    fun testViewStartsWithLongText() {
+        assertThat(lastText).isEqualTo(longText)
+    }
+
+    @Test
+    fun testListenerNotNull() {
+        assertThat(onMeasureListenerCaptor.value).isNotNull()
+    }
+
+    @Test
+    fun testLotsOfSpaceUseLongText() {
+        onMeasureListenerCaptor.value.onMeasureAction(10000)
+
+        testableLooper.processAllMessages()
+        assertThat(lastText).isEqualTo(longText)
+    }
+
+    @Test
+    fun testSmallSpaceUseEmpty() {
+        onMeasureListenerCaptor.value.onMeasureAction(1)
+        testableLooper.processAllMessages()
+
+        assertThat(lastText).isEmpty()
+    }
+
+    @Test
+    fun testSpaceInBetweenUseShortText() {
+        val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt()
+
+        onMeasureListenerCaptor.value.onMeasureAction(average)
+        testableLooper.processAllMessages()
+
+        assertThat(lastText).isEqualTo(shortText)
+    }
+
+    @Test
+    fun testSwitchBackToLonger() {
+        onMeasureListenerCaptor.value.onMeasureAction(1)
+        testableLooper.processAllMessages()
+
+        onMeasureListenerCaptor.value.onMeasureAction(10000)
+        testableLooper.processAllMessages()
+
+        assertThat(lastText).isEqualTo(longText)
+    }
+
+    @Test
+    fun testNoSwitchingWhenFrozen() {
+        `when`(view.freezeSwitching).thenReturn(true)
+
+        val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt()
+        onMeasureListenerCaptor.value.onMeasureAction(average)
+        testableLooper.processAllMessages()
+        assertThat(lastText).isEqualTo(longText)
+
+        onMeasureListenerCaptor.value.onMeasureAction(1)
+        testableLooper.processAllMessages()
+        assertThat(lastText).isEqualTo(longText)
+    }
+
+    private fun getTextLength(text: CharSequence): Float {
+        return text.length * CHAR_WIDTH
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index 9c47f19..e6dc4db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -96,6 +96,8 @@
     DumpManager mDumpManager;
     @Mock
     OverlayManagerTransaction.Builder mTransactionBuilder;
+    @Mock
+    Runnable mOnOverlaysApplied;
 
     private ThemeOverlayApplier mManager;
     private boolean mGetOverlayInfoEnabled = true;
@@ -103,7 +105,8 @@
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(),
+        mManager = new ThemeOverlayApplier(mOverlayManager,
+                MoreExecutors.directExecutor(), MoreExecutors.directExecutor(),
                 LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager) {
             @Override
             protected OverlayManagerTransaction.Builder getTransactionBuilder() {
@@ -173,7 +176,7 @@
     @Test
     public void allCategoriesSpecified_allEnabledExclusively() {
         mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
-                TEST_USER_HANDLES);
+                TEST_USER_HANDLES, mOnOverlaysApplied);
         verify(mOverlayManager).commit(any());
 
         for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
@@ -185,7 +188,7 @@
     @Test
     public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() {
         mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
-                TEST_USER_HANDLES);
+                TEST_USER_HANDLES, mOnOverlaysApplied);
 
         for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) {
             if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) {
@@ -202,8 +205,9 @@
     public void allCategoriesSpecified_enabledForAllUserHandles() {
         Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
         mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
-                userHandles);
+                userHandles, mOnOverlaysApplied);
 
+        verify(mOnOverlaysApplied).run();
         for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
             verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
                     eq(TEST_USER.getIdentifier()));
@@ -219,7 +223,7 @@
 
         Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
         mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
-                userHandles);
+                userHandles, mOnOverlaysApplied);
 
         for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
             verify(mTransactionBuilder, never()).setEnabled(eq(overlayPackage), eq(true),
@@ -233,7 +237,7 @@
                 mock(FabricatedOverlay.class)
         };
         mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation,
-                TEST_USER.getIdentifier(), TEST_USER_HANDLES);
+                TEST_USER.getIdentifier(), TEST_USER_HANDLES, mOnOverlaysApplied);
 
         for (FabricatedOverlay overlay : pendingCreation) {
             verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay));
@@ -247,7 +251,7 @@
         categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID);
 
         mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(),
-                TEST_USER_HANDLES);
+                TEST_USER_HANDLES, mOnOverlaysApplied);
 
         for (OverlayIdentifier overlayPackage : categoryToPackage.values()) {
             verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
@@ -264,7 +268,7 @@
     @Test
     public void zeroCategoriesSpecified_allDisabled() {
         mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER.getIdentifier(),
-                TEST_USER_HANDLES);
+                TEST_USER_HANDLES, mOnOverlaysApplied);
 
         for (String category : THEME_CATEGORIES) {
             verify(mTransactionBuilder).setEnabled(
@@ -279,7 +283,7 @@
         categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category"));
 
         mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(),
-                TEST_USER_HANDLES);
+                TEST_USER_HANDLES, mOnOverlaysApplied);
 
         verify(mTransactionBuilder, never()).setEnabled(
                 eq(new OverlayIdentifier("com.example.blah.category")), eq(false),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 07d3fc2..d3820f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -161,7 +161,7 @@
                 ArgumentCaptor.forClass(Map.class);
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
 
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
@@ -183,7 +183,7 @@
         mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED));
         mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
                 null, null), WallpaperManager.FLAG_SYSTEM);
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -204,7 +204,7 @@
                 ArgumentCaptor.forClass(Map.class);
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
 
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
@@ -240,7 +240,7 @@
                 .isFalse();
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -270,7 +270,7 @@
                 "android.theme.customization.color_both\":\"0")).isTrue();
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -300,7 +300,7 @@
                 "android.theme.customization.color_both\":\"1")).isTrue();
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -327,7 +327,7 @@
         assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index"))
                 .isFalse();
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -354,7 +354,7 @@
         assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index"))
                 .isFalse();
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -382,7 +382,7 @@
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -411,14 +411,14 @@
 
 
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
     public void onProfileAdded_setsTheme() {
         mBroadcastReceiver.getValue().onReceive(null,
                 new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED));
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -428,7 +428,7 @@
         mBroadcastReceiver.getValue().onReceive(null,
                 new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED));
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -438,7 +438,7 @@
         mBroadcastReceiver.getValue().onReceive(null,
                 new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED));
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -450,7 +450,7 @@
                 Color.valueOf(Color.BLUE), null);
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
 
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
 
         // Regression test: null events should not reset the internal state and allow colors to be
         // applied again.
@@ -458,11 +458,11 @@
         mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED));
         mColorsListener.getValue().onColorsChanged(null, WallpaperManager.FLAG_SYSTEM);
         verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
-                any());
+                any(), any());
         mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.GREEN),
                 null, null), WallpaperManager.FLAG_SYSTEM);
         verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
-                any());
+                any(), any());
     }
 
     @Test
@@ -499,7 +499,7 @@
         verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture());
 
         // Colors were applied during controller initialization.
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
         clearInvocations(mThemeOverlayApplier);
     }
 
@@ -533,7 +533,7 @@
         verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture());
 
         // Colors were applied during controller initialization.
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
         clearInvocations(mThemeOverlayApplier);
 
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -542,12 +542,12 @@
 
         // Defers event because we already have initial colors.
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
 
         // Then event happens after setup phase is over.
         when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
         mDeviceProvisionedListener.getValue().onUserSetupChanged();
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -568,11 +568,11 @@
                 Color.valueOf(Color.RED), null);
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
 
         mWakefulnessLifecycle.dispatchFinishedGoingToSleep();
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -592,10 +592,10 @@
                 Color.valueOf(Color.RED), null);
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
 
         mWakefulnessLifecycleObserver.getValue().onFinishedGoingToSleep();
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -614,7 +614,7 @@
                 ArgumentCaptor.forClass(Map.class);
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
 
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
new file mode 100644
index 0000000..eebcbe6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 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.usb
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.hardware.usb.IUsbSerialReader
+import android.hardware.usb.UsbAccessory
+import android.hardware.usb.UsbManager
+import android.testing.AndroidTestingRunner
+import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.lang.Exception
+
+/**
+ * UsbPermissionActivityTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UsbPermissionActivityTest : SysuiTestCase() {
+
+    class UsbPermissionActivityTestable : UsbPermissionActivity()
+
+    @Rule
+    @JvmField
+    var activityRule = ActivityTestRule<UsbPermissionActivityTestable>(
+            UsbPermissionActivityTestable::class.java, false, false)
+
+    private val activityIntent = Intent(mContext, UsbPermissionActivityTestable::class.java)
+            .apply {
+                flags = Intent.FLAG_ACTIVITY_NEW_TASK
+                putExtra(UsbManager.EXTRA_PACKAGE, "com.android.systemui")
+                putExtra(Intent.EXTRA_INTENT, PendingIntent.getBroadcast(
+                        mContext,
+                        334,
+                        Intent("NO_ACTION"),
+                        PendingIntent.FLAG_MUTABLE))
+                putExtra(UsbManager.EXTRA_ACCESSORY, UsbAccessory(
+                        "manufacturer",
+                        "model",
+                        "description",
+                        "version",
+                        "uri",
+                        object : IUsbSerialReader.Stub() {
+                            override fun getSerial(packageName: String): String {
+                                return "serial"
+                            }
+                        }))
+            }
+
+    @Before
+    fun setUp() {
+        activityRule.launchActivity(activityIntent)
+    }
+
+    @After
+    fun tearDown() {
+        activityRule.finishActivity()
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testHideNonSystemOverlay() {
+        assertThat(activityRule.activity.window.attributes.privateFlags and
+                SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+                .isEqualTo(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
index a34c598..0e9d96c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.util.sensors;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -338,30 +340,25 @@
     @Test
     public void testSecondaryCancelsSecondary() {
         TestableListener listener = new TestableListener();
-        ThresholdSensor.Listener cancelingListener = new ThresholdSensor.Listener() {
-            @Override
-            public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent event) {
-                mProximitySensor.pause();
-            }
-        };
+        ThresholdSensor.Listener cancelingListener = event -> mProximitySensor.pause();
 
         mProximitySensor.register(listener);
         mProximitySensor.register(cancelingListener);
-        assertNull(listener.mLastEvent);
-        assertEquals(0, listener.mCallCount);
+        assertThat(listener.mLastEvent).isNull();
+        assertThat(listener.mCallCount).isEqualTo(0);
 
         mThresholdSensorPrimary.triggerEvent(true, 0);
-        assertNull(listener.mLastEvent);
-        assertEquals(0, listener.mCallCount);
+        assertThat(listener.mLastEvent).isNull();
+        assertThat(listener.mCallCount).isEqualTo(0);
         mThresholdSensorSecondary.triggerEvent(true, 0);
-        assertTrue(listener.mLastEvent.getBelow());
-        assertEquals(1, listener.mCallCount);
+        assertThat(listener.mLastEvent.getBelow()).isTrue();
+        assertThat(listener.mCallCount).isEqualTo(1);
 
         // The proximity sensor should now be canceled. Advancing the clock should do nothing.
-        assertEquals(0, mFakeExecutor.numPending());
+        assertThat(mFakeExecutor.numPending()).isEqualTo(0);
         mThresholdSensorSecondary.triggerEvent(false, 1);
-        assertTrue(listener.mLastEvent.getBelow());
-        assertEquals(1, listener.mCallCount);
+        assertThat(listener.mLastEvent.getBelow()).isTrue();
+        assertThat(listener.mCallCount).isEqualTo(1);
 
         mProximitySensor.unregister(listener);
     }
@@ -372,33 +369,66 @@
 
         TestableListener listener = new TestableListener();
 
-        // WE immediately register the secondary sensor.
+        // We immediately register the secondary sensor.
         mProximitySensor.register(listener);
-        assertFalse(mThresholdSensorPrimary.isPaused());
-        assertFalse(mThresholdSensorSecondary.isPaused());
-        assertNull(listener.mLastEvent);
-        assertEquals(0, listener.mCallCount);
+        assertThat(mThresholdSensorPrimary.isPaused()).isTrue();
+        assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+        assertThat(listener.mLastEvent).isNull();
+        assertThat(listener.mCallCount).isEqualTo(0);
 
         mThresholdSensorPrimary.triggerEvent(true, 0);
-        assertNull(listener.mLastEvent);
-        assertEquals(0, listener.mCallCount);
+        assertThat(listener.mLastEvent).isNull();
+        assertThat(listener.mCallCount).isEqualTo(0);
         mThresholdSensorSecondary.triggerEvent(true, 0);
-        assertTrue(listener.mLastEvent.getBelow());
-        assertEquals(1, listener.mCallCount);
+        assertThat(listener.mLastEvent.getBelow()).isTrue();
+        assertThat(listener.mCallCount).isEqualTo(1);
 
         // The secondary sensor should now remain resumed indefinitely.
-        assertFalse(mThresholdSensorSecondary.isPaused());
+        assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
         mThresholdSensorSecondary.triggerEvent(false, 1);
-        assertFalse(listener.mLastEvent.getBelow());
-        assertEquals(2, listener.mCallCount);
+        assertThat(listener.mLastEvent.getBelow()).isFalse();
+        assertThat(listener.mCallCount).isEqualTo(2);
 
         // The secondary is still running, and not polling with the executor.
-        assertFalse(mThresholdSensorSecondary.isPaused());
-        assertEquals(0, mFakeExecutor.numPending());
+        assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+        assertThat(mFakeExecutor.numPending()).isEqualTo(0);
 
         mProximitySensor.unregister(listener);
     }
 
+    @Test
+    public void testSecondaryPausesPrimary() {
+        TestableListener listener = new TestableListener();
+
+        mProximitySensor.register(listener);
+
+        assertThat(mThresholdSensorPrimary.isPaused()).isFalse();
+        assertThat(mThresholdSensorSecondary.isPaused()).isTrue();
+
+        mProximitySensor.setSecondarySafe(true);
+
+        assertThat(mThresholdSensorPrimary.isPaused()).isTrue();
+        assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+    }
+
+    @Test
+    public void testSecondaryResumesPrimary() {
+        mProximitySensor.setSecondarySafe(true);
+
+        TestableListener listener = new TestableListener();
+        mProximitySensor.register(listener);
+
+        assertThat(mThresholdSensorPrimary.isPaused()).isTrue();
+        assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+
+        mProximitySensor.setSecondarySafe(false);
+
+        assertThat(mThresholdSensorPrimary.isPaused()).isFalse();
+        assertThat(mThresholdSensorSecondary.isPaused()).isTrue();
+
+
+    }
+
     private static class TestableListener implements ThresholdSensor.Listener {
         ThresholdSensor.ThresholdSensorEvent mLastEvent;
         int mCallCount = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
index 6976422..7bb2674 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
@@ -31,6 +31,7 @@
     private final Map<SettingsKey, String> mValues = new HashMap<>();
     private final Map<SettingsKey, List<ContentObserver>> mContentObservers =
             new HashMap<>();
+    private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>();
 
     public static final Uri CONTENT_URI = Uri.parse("content://settings/fake");
 
@@ -55,9 +56,15 @@
     @Override
     public void registerContentObserverForUser(Uri uri, boolean notifyDescendents,
             ContentObserver settingsObserver, int userHandle) {
-        SettingsKey key = new SettingsKey(userHandle, uri.toString());
-        mContentObservers.putIfAbsent(key, new ArrayList<>());
-        List<ContentObserver> observers = mContentObservers.get(key);
+        List<ContentObserver> observers;
+        if (userHandle == UserHandle.USER_ALL) {
+            mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>());
+            observers = mContentObserversAllUsers.get(uri.toString());
+        } else {
+            SettingsKey key = new SettingsKey(userHandle, uri.toString());
+            mContentObservers.putIfAbsent(key, new ArrayList<>());
+            observers = mContentObservers.get(key);
+        }
         observers.add(settingsObserver);
     }
 
@@ -67,6 +74,10 @@
             List<ContentObserver> observers = mContentObservers.get(key);
             observers.remove(settingsObserver);
         }
+        for (String key : mContentObserversAllUsers.keySet()) {
+            List<ContentObserver> observers = mContentObserversAllUsers.get(key);
+            observers.remove(settingsObserver);
+        }
     }
 
     @Override
@@ -114,6 +125,10 @@
         for (ContentObserver observer : mContentObservers.getOrDefault(key, new ArrayList<>())) {
             observer.dispatchChange(false, List.of(uri), userHandle);
         }
+        for (ContentObserver observer :
+                mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) {
+            observer.dispatchChange(false, List.of(uri), userHandle);
+        }
         return true;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
index 0d560f2..34cae58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.database.ContentObserver;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 
@@ -89,6 +90,16 @@
     }
 
     @Test
+    public void testRegisterContentObserverAllUsers() {
+        mFakeSettings.registerContentObserverForUser(
+                mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL);
+
+        mFakeSettings.putString("cat", "hat");
+
+        verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt());
+    }
+
+    @Test
     public void testUnregisterContentObserver() {
         mFakeSettings.registerContentObserver("cat", mContentObserver);
         mFakeSettings.unregisterContentObserver(mContentObserver);
@@ -98,4 +109,16 @@
         verify(mContentObserver, never()).dispatchChange(
                 anyBoolean(), any(Collection.class), anyInt());
     }
+
+    @Test
+    public void testUnregisterContentObserverAllUsers() {
+        mFakeSettings.registerContentObserverForUser(
+                mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL);
+        mFakeSettings.unregisterContentObserver(mContentObserver);
+
+        mFakeSettings.putString("cat", "hat");
+
+        verify(mContentObserver, never()).dispatchChange(
+                anyBoolean(), any(Collection.class), anyInt());
+    }
 }
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 0c633ca..7ba032f 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -478,7 +478,7 @@
                     for (int uid : uidsToRemove) {
                         FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, -1, uid,
                                 FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__REMOVED);
-                        mStats.removeIsolatedUidLocked(uid, SystemClock.elapsedRealtime(),
+                        mStats.maybeRemoveIsolatedUidLocked(uid, SystemClock.elapsedRealtime(),
                                 SystemClock.uptimeMillis());
                     }
                     mStats.clearPendingRemovedUids();
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index f32aa22..9da6d52 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -157,6 +157,9 @@
     static final int SYNC_RECEIVED_WHILE_FROZEN = 1;
     static final int ASYNC_RECEIVED_WHILE_FROZEN = 2;
 
+    // Bitfield values for sync transactions received by frozen binder threads
+    static final int TXNS_PENDING_WHILE_FROZEN = 4;
+
     /**
      * This thread must be moved to the system background cpuset.
      * If that doesn't happen, it's probably going to draw a lot of power.
@@ -611,8 +614,9 @@
      * binder for the specificed pid.
      *
      * @throws RuntimeException in case a flush/freeze operation could not complete successfully.
+     * @return 0 if success, or -EAGAIN indicating there's pending transaction.
      */
-    private static native void freezeBinder(int pid, boolean freeze);
+    private static native int freezeBinder(int pid, boolean freeze);
 
     /**
      * Retrieves binder freeze info about a process.
@@ -948,7 +952,7 @@
             int freezeInfo = getBinderFreezeInfo(pid);
 
             if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
-                Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " "
+                Slog.d(TAG_AM, "pid " + pid + " " + app.processName
                         + " received sync transactions while frozen, killing");
                 app.killLocked("Sync transaction while in frozen state",
                         ApplicationExitInfo.REASON_OTHER,
@@ -956,8 +960,8 @@
                 processKilled = true;
             }
 
-            if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
-                Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " "
+            if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0 && DEBUG_FREEZER) {
+                Slog.d(TAG_AM, "pid " + pid + " " + app.processName
                         + " received async transactions while frozen");
             }
         } catch (Exception e) {
@@ -1292,7 +1296,9 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case SET_FROZEN_PROCESS_MSG:
-                    freezeProcess((ProcessRecord) msg.obj);
+                    synchronized (mAm) {
+                        freezeProcess((ProcessRecord) msg.obj);
+                    }
                     break;
                 case REPORT_UNFREEZE_MSG:
                     int pid = msg.arg1;
@@ -1306,6 +1312,15 @@
             }
         }
 
+        @GuardedBy({"mAm", "mProcLock"})
+        private void rescheduleFreeze(final ProcessRecord proc, final String reason) {
+            Slog.d(TAG_AM, "Reschedule freeze for process " + proc.getPid()
+                    + " " + proc.processName + " (" + reason + ")");
+            unfreezeAppLSP(proc);
+            freezeAppAsyncLSP(proc);
+        }
+
+        @GuardedBy({"mAm"})
         private void freezeProcess(final ProcessRecord proc) {
             int pid = proc.getPid(); // Unlocked intentionally
             final String name = proc.processName;
@@ -1355,10 +1370,15 @@
                     return;
                 }
 
+                Slog.d(TAG_AM, "freezing " + pid + " " + name);
+
                 // Freeze binder interface before the process, to flush any
                 // transactions that might be pending.
                 try {
-                    freezeBinder(pid, true);
+                    if (freezeBinder(pid, true) != 0) {
+                        rescheduleFreeze(proc, "outstanding txns");
+                        return;
+                    }
                 } catch (RuntimeException e) {
                     Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
                     mFreezeHandler.post(() -> {
@@ -1404,24 +1424,36 @@
 
             try {
                 // post-check to prevent races
+                int freezeInfo = getBinderFreezeInfo(pid);
+
+                if ((freezeInfo & TXNS_PENDING_WHILE_FROZEN) != 0) {
+                    synchronized (mProcLock) {
+                        rescheduleFreeze(proc, "new pending txns");
+                    }
+                    return;
+                }
+            } catch (RuntimeException e) {
+                Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
+                mFreezeHandler.post(() -> {
+                    synchronized (mAm) {
+                        proc.killLocked("Unable to freeze binder interface",
+                                ApplicationExitInfo.REASON_OTHER,
+                                ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
+                    }
+                });
+            }
+
+            try {
+                // post-check to prevent races
                 if (mProcLocksReader.hasFileLocks(pid)) {
                     if (DEBUG_FREEZER) {
                         Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze");
                     }
-
-                    synchronized (mAm) {
-                        synchronized (mProcLock) {
-                            unfreezeAppLSP(proc);
-                        }
-                    }
+                    unfreezeAppLSP(proc);
                 }
             } catch (Exception e) {
                 Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
-                synchronized (mAm) {
-                    synchronized (mProcLock) {
-                        unfreezeAppLSP(proc);
-                    }
-                }
+                unfreezeAppLSP(proc);
             }
         }
 
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 5c9d385..2e3e635 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -94,8 +94,6 @@
         sGlobalSettingToTypeMap.put(
                 Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES, String.class);
         sGlobalSettingToTypeMap.put(
-                Settings.Global.ANGLE_ALLOWLIST, String.class);
-        sGlobalSettingToTypeMap.put(
                 Settings.Global.ANGLE_EGL_FEATURES, String.class);
         sGlobalSettingToTypeMap.put(
                 Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX, String.class);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 5c19ceb..ff480d1 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -72,6 +72,7 @@
 import static com.android.server.am.ProcessList.TAG_PROCESS_OBSERVERS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
@@ -114,6 +115,8 @@
 import com.android.server.wm.WindowProcessController;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -171,6 +174,27 @@
     @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
     static final long USE_SHORT_FGS_USAGE_INTERACTION_TIME = 183972877L;
 
+    static final int CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY = 0;
+    static final int CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY = 1;
+    static final int CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME = 2;
+
+    @IntDef(prefix = { "CACHED_COMPAT_CHANGE_" }, value = {
+        CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY,
+        CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY,
+        CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    static @interface CachedCompatChangeId{}
+
+    /**
+     * Mapping from CACHED_COMPAT_CHANGE_* to the actual compat change id.
+     */
+    static final long[] CACHED_COMPAT_CHANGE_IDS_MAPPING = new long[] {
+        PROCESS_CAPABILITY_CHANGE_ID,
+        CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID,
+        USE_SHORT_FGS_USAGE_INTERACTION_TIME,
+    };
+
     /**
      * For some direct access we need to power manager.
      */
@@ -368,6 +392,12 @@
         return mPlatformCompatCache;
     }
 
+    boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId, ApplicationInfo app,
+            boolean defaultValue) {
+        return getPlatformCompatCache().isChangeEnabled(
+                CACHED_COMPAT_CHANGE_IDS_MAPPING[cachedCompatChangeId], app, defaultValue);
+    }
+
     OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) {
         this(service, processList, activeUids, createAdjusterThread());
     }
@@ -1929,12 +1959,8 @@
                             (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
                                     != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
 
-                    boolean enabled = false;
-                    try {
-                        enabled = getPlatformCompatCache().isChangeEnabled(
-                                CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID, s.appInfo);
-                    } catch (RemoteException e) {
-                    }
+                    final boolean enabled = state.getCachedCompatChange(
+                            CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY);
                     if (enabled) {
                         capabilityFromFGS |=
                                 (fgsType & FOREGROUND_SERVICE_TYPE_CAMERA)
@@ -2151,12 +2177,8 @@
                                 // to client's state.
                                 clientProcState = PROCESS_STATE_BOUND_TOP;
                                 state.bumpAllowStartFgsState(PROCESS_STATE_BOUND_TOP);
-                                boolean enabled = false;
-                                try {
-                                    enabled = getPlatformCompatCache().isChangeEnabled(
-                                            PROCESS_CAPABILITY_CHANGE_ID, client.info);
-                                } catch (RemoteException e) {
-                                }
+                                final boolean enabled = cstate.getCachedCompatChange(
+                                        CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY);
                                 if (enabled) {
                                     if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
                                         // TOP process passes all capabilities to the service.
@@ -2800,8 +2822,8 @@
                 state.setProcStateChanged(true);
             }
         } else if (state.hasReportedInteraction()) {
-            final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
-                    USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+            final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+                    CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
             final long interactionThreshold = fgsInteractionChangeEnabled
                     ? mConstants.USAGE_STATS_INTERACTION_INTERVAL_POST_S
                     : mConstants.USAGE_STATS_INTERACTION_INTERVAL_PRE_S;
@@ -2811,8 +2833,8 @@
                 maybeUpdateUsageStatsLSP(app, nowElapsed);
             }
         } else {
-            final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
-                    USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+            final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+                    CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
             final long interactionThreshold = fgsInteractionChangeEnabled
                     ? mConstants.SERVICE_USAGE_INTERACTION_TIME_POST_S
                     : mConstants.SERVICE_USAGE_INTERACTION_TIME_PRE_S;
@@ -2901,8 +2923,8 @@
         if (mService.mUsageStatsService == null) {
             return;
         }
-        final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
-                USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+        final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+                CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
         boolean isInteraction;
         // To avoid some abuse patterns, we are going to be careful about what we consider
         // to be an app interaction.  Being the top activity doesn't count while the display
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index dc6bcd8..d4474d6 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -21,6 +21,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.OomAdjuster.CachedCompatChangeId;
 import static com.android.server.am.ProcessRecord.TAG;
 
 import android.annotation.ElapsedRealtimeLong;
@@ -377,6 +378,16 @@
     @GuardedBy("mService")
     private int mCachedIsReceivingBroadcast = VALUE_INVALID;
 
+    /**
+     * Cache the return value of PlatformCompat.isChangeEnabled().
+     */
+    @GuardedBy("mService")
+    private int[] mCachedCompatChanges = new int[] {
+        VALUE_INVALID, // CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY
+        VALUE_INVALID, // CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY
+        VALUE_INVALID, // CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME
+    };
+
     @GuardedBy("mService")
     private int mCachedAdj = ProcessList.INVALID_ADJ;
     @GuardedBy("mService")
@@ -1009,6 +1020,16 @@
     }
 
     @GuardedBy("mService")
+    boolean getCachedCompatChange(@CachedCompatChangeId int cachedCompatChangeId) {
+        if (mCachedCompatChanges[cachedCompatChangeId] == VALUE_INVALID) {
+            mCachedCompatChanges[cachedCompatChangeId] = mService.mOomAdjuster
+                    .isChangeEnabled(cachedCompatChangeId, mApp.info, false /* default */)
+                    ? VALUE_TRUE : VALUE_FALSE;
+        }
+        return mCachedCompatChanges[cachedCompatChangeId] == VALUE_TRUE;
+    }
+
+    @GuardedBy("mService")
     void computeOomAdjFromActivitiesIfNecessary(OomAdjuster.ComputeOomAdjWindowCallback callback,
             int adj, boolean foregroundActivities, boolean hasVisibleActivities, int procState,
             int schedGroup, int appUid, int logUid, int processCurTop) {
@@ -1088,6 +1109,9 @@
         mCurSchedGroup = mSetSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
         mCurProcState = mCurRawProcState = mSetProcState = mAllowStartFgsState =
                 PROCESS_STATE_NONEXISTENT;
+        for (int i = 0; i < mCachedCompatChanges.length; i++) {
+            mCachedCompatChanges[i] = VALUE_INVALID;
+        }
     }
 
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index af8d7a6..3003c52 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -272,6 +272,13 @@
                 "com.android.graphics.intervention.wm.allowDownscale";
 
         /**
+         * Metadata that can be included in the app manifest to allow/disallow any ANGLE
+         * interventions. Default value is TRUE.
+         */
+        public static final String METADATA_ANGLE_ALLOW_ANGLE =
+                "com.android.graphics.intervention.angle.allowAngle";
+
+        /**
          * Metadata that needs to be included in the app manifest to OPT-IN to PERFORMANCE mode.
          * This means the app will assume full responsibility for the experience provided by this
          * mode and the system will enable no window manager downscaling.
@@ -294,6 +301,7 @@
         private boolean mPerfModeOptedIn;
         private boolean mBatteryModeOptedIn;
         private boolean mAllowDownscale;
+        private boolean mAllowAngle;
 
         GamePackageConfiguration(String packageName, int userId) {
             mPackageName = packageName;
@@ -305,10 +313,12 @@
                     mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
                     mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
                     mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
+                    mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
                 } else {
                     mPerfModeOptedIn = false;
                     mBatteryModeOptedIn = false;
                     mAllowDownscale = true;
+                    mAllowAngle = true;
                 }
             } catch (PackageManager.NameNotFoundException e) {
                 // Not all packages are installed, hence ignore those that are not installed yet.
@@ -340,14 +350,26 @@
             public static final String MODE_KEY = "mode";
             public static final String SCALING_KEY = "downscaleFactor";
             public static final String DEFAULT_SCALING = "1.0";
+            public static final String ANGLE_KEY = "useAngle";
 
             private final @GameMode int mGameMode;
             private final String mScaling;
+            private final boolean mUseAngle;
 
             GameModeConfiguration(KeyValueListParser parser) {
                 mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED);
-                mScaling = !mAllowDownscale || isGameModeOptedIn(mGameMode)
+                // isGameModeOptedIn() returns if an app will handle all of the changes necessary
+                // for a particular game mode. If so, the Android framework (i.e.
+                // GameManagerService) will not do anything for the app (like window scaling or
+                // using ANGLE).
+                mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
                         ? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING);
+                // We only want to use ANGLE if:
+                // - We're allowed to use ANGLE (the app hasn't opted out via the manifest) AND
+                // - The app has not opted in to performing the work itself AND
+                // - The Phenotype config has enabled it.
+                mUseAngle = mAllowAngle && !willGamePerformOptimizations(mGameMode)
+                        && parser.getBoolean(ANGLE_KEY, false);
             }
 
             public int getGameMode() {
@@ -358,6 +380,10 @@
                 return mScaling;
             }
 
+            public boolean getUseAngle() {
+                return mUseAngle;
+            }
+
             public boolean isValid() {
                 return (mGameMode == GameManager.GAME_MODE_PERFORMANCE
                         || mGameMode == GameManager.GAME_MODE_BATTERY)
@@ -368,7 +394,8 @@
              * @hide
              */
             public String toString() {
-                return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + "]";
+                return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + ",Use Angle:"
+                        + mUseAngle + "]";
             }
 
             /**
@@ -384,13 +411,14 @@
         }
 
         /**
-         * Gets whether a package has opted into a game mode via its manifest.
+         * Returns if the app will assume full responsibility for the experience provided by this
+         * mode. If True, the system will not perform any interventions for the app.
          *
          * @return True if the app package has specified in its metadata either:
          * "com.android.app.gamemode.performance.enabled" or
          * "com.android.app.gamemode.battery.enabled" with a value of "true"
          */
-        public boolean isGameModeOptedIn(@GameMode int gameMode) {
+        public boolean willGamePerformOptimizations(@GameMode int gameMode) {
             return (mBatteryModeOptedIn && gameMode == GameManager.GAME_MODE_BATTERY)
                     || (mPerfModeOptedIn && gameMode == GameManager.GAME_MODE_PERFORMANCE);
         }
@@ -631,7 +659,34 @@
                 mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
             }
         }
-        updateCompatModeDownscale(packageName, gameMode);
+        updateInterventions(packageName, gameMode);
+    }
+
+    /**
+     * Get if ANGLE is enabled for the package for the currently enabled game mode.
+     * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
+     */
+    @Override
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+    public @GameMode boolean getAngleEnabled(String packageName, int userId)
+            throws SecurityException {
+        final int gameMode = getGameMode(packageName, userId);
+        if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+            return false;
+        }
+
+        synchronized (mDeviceConfigLock) {
+            final GamePackageConfiguration config = mConfigs.get(packageName);
+            if (config == null) {
+                return false;
+            }
+            GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
+                    config.getGameModeConfiguration(gameMode);
+            if (gameModeConfiguration == null) {
+                return false;
+            }
+            return gameModeConfiguration.getUseAngle();
+        }
     }
 
     /**
@@ -753,7 +808,7 @@
             if (DEBUG) {
                 Slog.v(TAG, dumpDeviceConfigs());
             }
-            if (packageConfig.isGameModeOptedIn(gameMode)) {
+            if (packageConfig.willGamePerformOptimizations(gameMode)) {
                 disableCompatScale(packageName);
                 return;
             }
@@ -782,6 +837,17 @@
         return (bitField & modeToBitmask(gameMode)) != 0;
     }
 
+    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+    private void updateUseAngle(String packageName, @GameMode int gameMode) {
+        // TODO (b/188475576): Nothing to do yet. Remove if it's still empty when we're ready to
+        // ship.
+    }
+
+    private void updateInterventions(String packageName, @GameMode int gameMode) {
+        updateCompatModeDownscale(packageName, gameMode);
+        updateUseAngle(packageName, gameMode);
+    }
+
     /**
      * @hide
      */
@@ -839,11 +905,11 @@
                     if (newGameMode != gameMode) {
                         setGameMode(packageName, newGameMode, userId);
                     }
-                    updateCompatModeDownscale(packageName, gameMode);
+                    updateInterventions(packageName, gameMode);
                 }
             }
         } catch (Exception e) {
-            Slog.e(TAG, "Failed to update compat modes for user: " + userId);
+            Slog.e(TAG, "Failed to update compat modes for user " + userId + ": " + e);
         }
     }
 
@@ -851,7 +917,7 @@
         final List<PackageInfo> packages =
                 mPackageManager.getInstalledPackagesAsUser(0, userId);
         return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category
-                        == ApplicationInfo.CATEGORY_GAME)
+                == ApplicationInfo.CATEGORY_GAME)
                 .map(e -> e.packageName)
                 .toArray(String[]::new);
     }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 38f71ba..84a3060 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1812,10 +1812,10 @@
         if (client == null) {
             return;
         }
-        Log.w(TAG, "Speaker client died");
+        Log.w(TAG, "Communication client died");
         setCommunicationRouteForClient(
-                client.getBinder(), client.getPid(), null,
-                BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied");
+                client.getBinder(), client.getPid(), null, BtHelper.SCO_MODE_UNDEFINED,
+                "onCommunicationRouteClientDied");
     }
 
     /**
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index bf5f4c2..acf4328 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2823,8 +2823,8 @@
         if (uid == android.os.Process.SYSTEM_UID) {
             uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));
         }
-        if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
-                != AppOpsManager.MODE_ALLOWED) {
+        // validate calling package and app op
+        if (!checkNoteAppOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)) {
             return;
         }
 
@@ -3547,8 +3547,7 @@
         if (uid == android.os.Process.SYSTEM_UID) {
             uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));
         }
-        if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
-                != AppOpsManager.MODE_ALLOWED) {
+        if (!checkNoteAppOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)) {
             return;
         }
 
@@ -3983,8 +3982,7 @@
             uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
         }
         // If OP_AUDIO_MASTER_VOLUME is set, disallow unmuting.
-        if (!mute && mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
-                != AppOpsManager.MODE_ALLOWED) {
+        if (!mute && !checkNoteAppOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)) {
             return;
         }
         if (userId != UserHandle.getCallingUserId() &&
@@ -4115,8 +4113,7 @@
                         ? MediaMetrics.Value.MUTE : MediaMetrics.Value.UNMUTE);
 
         // If OP_MUTE_MICROPHONE is set, disallow unmuting.
-        if (!on && mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)
-                != AppOpsManager.MODE_ALLOWED) {
+        if (!on && !checkNoteAppOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)) {
             mmi.set(MediaMetrics.Property.EARLY_RETURN, "disallow unmuting").record();
             return;
         }
@@ -5295,6 +5292,10 @@
     // TODO investigate internal users due to deprecation of SDK API
     /** @see AudioManager#setBluetoothA2dpOn(boolean) */
     public void setBluetoothA2dpOn(boolean on) {
+        if (!checkAudioSettingsPermission("setBluetoothA2dpOn()")) {
+            return;
+        }
+
         // for logging only
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
@@ -5320,6 +5321,10 @@
 
     /** @see AudioManager#startBluetoothSco() */
     public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
+        if (!checkAudioSettingsPermission("startBluetoothSco()")) {
+            return;
+        }
+
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
         final int scoAudioMode =
@@ -5342,6 +5347,10 @@
 
     /** @see AudioManager#startBluetoothScoVirtualCall() */
     public void startBluetoothScoVirtualCall(IBinder cb) {
+        if (!checkAudioSettingsPermission("startBluetoothScoVirtualCall()")) {
+            return;
+        }
+
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
         final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()")
@@ -10554,4 +10563,31 @@
         }
         mFullVolumeDevices.remove(audioSystemDeviceOut);
     }
+
+    //====================
+    // Helper functions for app ops
+    //====================
+    /**
+     * Validates, and notes an app op for a given uid and package name.
+     * Validation comes from exception catching: a security exception indicates the package
+     * doesn't exist, an IAE indicates the uid and package don't match. The code only checks
+     * if exception was thrown for robustness to code changes in op validation
+     * @param op the app op to check
+     * @param uid the uid of the caller
+     * @param packageName the package to check
+     * @return true if the origin of the call is valid (no uid / package mismatch) and the caller
+     *      is allowed to perform the operation
+     */
+    private boolean checkNoteAppOp(int op, int uid, String packageName) {
+        try {
+            if (mAppOps.noteOp(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
+                return false;
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error noting op:" + op + " on uid:" + uid + " for package:"
+                    + packageName, e);
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 0cd2e3d..b42f898 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -206,7 +206,7 @@
         }
 
         @Override
-        public void authenticate(IBinder token, long sessionId, int userId,
+        public long authenticate(IBinder token, long sessionId, int userId,
                 IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo)
                 throws RemoteException {
             // Only allow internal clients to authenticate with a different userId.
@@ -223,18 +223,18 @@
 
             if (!checkAppOps(callingUid, opPackageName, "authenticate()")) {
                 authenticateFastFail("Denied by app ops: " + opPackageName, receiver);
-                return;
+                return -1;
             }
 
             if (token == null || receiver == null || opPackageName == null || promptInfo == null) {
                 authenticateFastFail(
                         "Unable to authenticate, one or more null arguments", receiver);
-                return;
+                return -1;
             }
 
             if (!Utils.isForeground(callingUid, callingPid)) {
                 authenticateFastFail("Caller is not foreground: " + opPackageName, receiver);
-                return;
+                return -1;
             }
 
             if (promptInfo.containsTestConfigurations()) {
@@ -251,7 +251,7 @@
 
             final long identity = Binder.clearCallingIdentity();
             try {
-                mBiometricService.authenticate(
+                return mBiometricService.authenticate(
                         token, sessionId, userId, receiver, opPackageName, promptInfo);
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -270,7 +270,7 @@
         }
 
         @Override
-        public void cancelAuthentication(IBinder token, String opPackageName)
+        public void cancelAuthentication(IBinder token, String opPackageName, long requestId)
                 throws RemoteException {
             checkPermission();
 
@@ -281,7 +281,7 @@
 
             final long identity = Binder.clearCallingIdentity();
             try {
-                mBiometricService.cancelAuthentication(token, opPackageName);
+                mBiometricService.cancelAuthentication(token, opPackageName, requestId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index bdde980..0da6a1b 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -128,6 +128,7 @@
     @VisibleForTesting final IBinder mToken;
     // Info to be shown on BiometricDialog when all cookies are returned.
     @VisibleForTesting final PromptInfo mPromptInfo;
+    private final long mRequestId;
     private final long mOperationId;
     private final int mUserId;
     private final IBiometricSensorReceiver mSensorReceiver;
@@ -142,6 +143,8 @@
     private @BiometricMultiSensorMode int mMultiSensorMode;
     private @MultiSensorState int mMultiSensorState;
     private int[] mSensors;
+    // TODO(b/197265902): merge into state
+    private boolean mCancelled;
     // For explicit confirmation, do not send to keystore until the user has confirmed
     // the authentication.
     private byte[] mTokenEscrow;
@@ -162,6 +165,7 @@
             @NonNull ClientDeathReceiver clientDeathReceiver,
             @NonNull PreAuthInfo preAuthInfo,
             @NonNull IBinder token,
+            long requestId,
             long operationId,
             int userId,
             @NonNull IBiometricSensorReceiver sensorReceiver,
@@ -179,6 +183,7 @@
         mClientDeathReceiver = clientDeathReceiver;
         mPreAuthInfo = preAuthInfo;
         mToken = token;
+        mRequestId = requestId;
         mOperationId = operationId;
         mUserId = userId;
         mSensorReceiver = sensorReceiver;
@@ -187,6 +192,7 @@
         mPromptInfo = promptInfo;
         mDebugEnabled = debugEnabled;
         mFingerprintSensorProperties = fingerprintSensorProperties;
+        mCancelled = false;
 
         try {
             mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */);
@@ -233,7 +239,7 @@
                 Slog.v(TAG, "waiting for cooking for sensor: " + sensor.id);
             }
             sensor.goToStateWaitingForCookie(requireConfirmation, mToken, mOperationId,
-                    mUserId, mSensorReceiver, mOpPackageName, cookie,
+                    mUserId, mSensorReceiver, mOpPackageName, mRequestId, cookie,
                     mPromptInfo.isAllowBackgroundAuthentication());
         }
     }
@@ -255,8 +261,9 @@
                     true /* credentialAllowed */,
                     false /* requireConfirmation */,
                     mUserId,
-                    mOpPackageName,
                     mOperationId,
+                    mOpPackageName,
+                    mRequestId,
                     mMultiSensorMode);
         } else if (!mPreAuthInfo.eligibleSensors.isEmpty()) {
             // Some combination of biometric or biometric|credential is requested
@@ -270,6 +277,11 @@
     }
 
     void onCookieReceived(int cookie) {
+        if (mCancelled) {
+            Slog.w(TAG, "Received cookie but already cancelled (ignoring): " + cookie);
+            return;
+        }
+
         for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
             sensor.goToStateCookieReturnedIfCookieMatches(cookie);
         }
@@ -301,8 +313,9 @@
                             mPreAuthInfo.shouldShowCredential(),
                             requireConfirmation,
                             mUserId,
-                            mOpPackageName,
                             mOperationId,
+                            mOpPackageName,
+                            mRequestId,
                             mMultiSensorMode);
                     mState = STATE_AUTH_STARTED;
                 } catch (RemoteException e) {
@@ -369,7 +382,7 @@
                 final boolean shouldCancel = filter.apply(sensor);
                 Slog.d(TAG, "sensorId: " + sensor.id + ", shouldCancel: " + shouldCancel);
                 if (shouldCancel) {
-                    sensor.goToStateCancelling(mToken, mOpPackageName);
+                    sensor.goToStateCancelling(mToken, mOpPackageName, mRequestId);
                 }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Unable to cancel authentication");
@@ -425,8 +438,9 @@
                             true /* credentialAllowed */,
                             false /* requireConfirmation */,
                             mUserId,
-                            mOpPackageName,
                             mOperationId,
+                            mOpPackageName,
+                            mRequestId,
                             mMultiSensorMode);
                 } else {
                     mClientReceiver.onError(modality, error, vendorCode);
@@ -775,6 +789,8 @@
      * @return true if this AuthSession is finished, e.g. should be set to null
      */
     boolean onCancelAuthSession(boolean force) {
+        mCancelled = true;
+
         final boolean authStarted = mState == STATE_AUTH_CALLED
                 || mState == STATE_AUTH_STARTED
                 || mState == STATE_AUTH_STARTED_UI_SHOWING;
@@ -820,6 +836,7 @@
         return Utils.isCredentialRequested(mPromptInfo);
     }
 
+    @VisibleForTesting
     boolean allCookiesReceived() {
         final int remainingCookies = mPreAuthInfo.numSensorsWaitingForCookie();
         Slog.d(TAG, "Remaining cookies: " + remainingCookies);
@@ -839,6 +856,10 @@
         return mState;
     }
 
+    long getRequestId() {
+        return mRequestId;
+    }
+
     private int statsModality() {
         int modality = 0;
 
@@ -901,7 +922,9 @@
     @Override
     public String toString() {
         return "State: " + mState
+                + ", cancelled: " + mCancelled
                 + ", isCrypto: " + isCrypto()
-                + ", PreAuthInfo: " + mPreAuthInfo;
+                + ", PreAuthInfo: " + mPreAuthInfo
+                + ", requestId: " + mRequestId;
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java
index 8a842b5..0333c3e 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensor.java
+++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java
@@ -108,11 +108,11 @@
 
     void goToStateWaitingForCookie(boolean requireConfirmation, IBinder token, long sessionId,
             int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
-            int cookie, boolean allowBackgroundAuthentication)
+            long requestId, int cookie, boolean allowBackgroundAuthentication)
             throws RemoteException {
         mCookie = cookie;
         impl.prepareForAuthentication(requireConfirmation, token,
-                sessionId, userId, sensorReceiver, opPackageName, mCookie,
+                sessionId, userId, sensorReceiver, opPackageName, requestId, mCookie,
                 allowBackgroundAuthentication);
         mSensorState = STATE_WAITING_FOR_COOKIE;
     }
@@ -129,8 +129,9 @@
         mSensorState = STATE_AUTHENTICATING;
     }
 
-    void goToStateCancelling(IBinder token, String opPackageName) throws RemoteException {
-        impl.cancelAuthenticationFromService(token, opPackageName);
+    void goToStateCancelling(IBinder token, String opPackageName, long requestId)
+            throws RemoteException {
+        impl.cancelAuthenticationFromService(token, opPackageName, requestId);
         mSensorState = STATE_CANCELING;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index b1d300c..e0775d4 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -83,6 +83,7 @@
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * System service that arbitrates the modality for BiometricPrompt to use.
@@ -115,6 +116,7 @@
     final SettingObserver mSettingObserver;
     private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
     private final Random mRandom = new Random();
+    @NonNull private final AtomicLong mRequestCounter;
 
     @VisibleForTesting
     IStatusBarService mStatusBarService;
@@ -194,6 +196,7 @@
                     SomeArgs args = (SomeArgs) msg.obj;
                     handleAuthenticate(
                             (IBinder) args.arg1 /* token */,
+                            (long) args.arg6 /* requestId */,
                             (long) args.arg2 /* operationId */,
                             args.argi1 /* userid */,
                             (IBiometricServiceReceiver) args.arg3 /* receiver */,
@@ -204,7 +207,9 @@
                 }
 
                 case MSG_CANCEL_AUTHENTICATION: {
-                    handleCancelAuthentication();
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleCancelAuthentication((long) args.arg3 /* requestId */);
+                    args.recycle();
                     break;
                 }
 
@@ -683,13 +688,13 @@
         }
 
         @Override // Binder call
-        public void authenticate(IBinder token, long operationId, int userId,
+        public long authenticate(IBinder token, long operationId, int userId,
                 IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo) {
             checkInternalPermission();
 
             if (token == null || receiver == null || opPackageName == null || promptInfo == null) {
                 Slog.e(TAG, "Unable to authenticate, one or more null arguments");
-                return;
+                return -1;
             }
 
             if (!Utils.isValidAuthenticatorConfig(promptInfo)) {
@@ -706,6 +711,8 @@
                 }
             }
 
+            final long requestId = mRequestCounter.incrementAndGet();
+
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = token;
             args.arg2 = operationId;
@@ -713,15 +720,23 @@
             args.arg3 = receiver;
             args.arg4 = opPackageName;
             args.arg5 = promptInfo;
+            args.arg6 = requestId;
 
             mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget();
+
+            return requestId;
         }
 
         @Override // Binder call
-        public void cancelAuthentication(IBinder token, String opPackageName) {
+        public void cancelAuthentication(IBinder token, String opPackageName, long requestId) {
             checkInternalPermission();
 
-            mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION).sendToTarget();
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = token;
+            args.arg2 = opPackageName;
+            args.arg3 = requestId;
+
+            mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION, args).sendToTarget();
         }
 
         @Override // Binder call
@@ -1111,6 +1126,10 @@
             return Settings.Secure.getInt(context.getContentResolver(),
                     CoexCoordinator.FACE_HAPTIC_DISABLE, 1) != 0;
         }
+
+        public AtomicLong getRequestGenerator() {
+            return new AtomicLong(0);
+        }
     }
 
     /**
@@ -1136,6 +1155,7 @@
         mEnabledOnKeyguardCallbacks = new ArrayList<>();
         mSettingObserver = mInjector.getSettingObserver(context, mHandler,
                 mEnabledOnKeyguardCallbacks);
+        mRequestCounter = mInjector.getRequestGenerator();
 
         // TODO(b/193089985) This logic lives here (outside of CoexCoordinator) so that it doesn't
         //  need to depend on context. We can remove this code once the advanced logic is enabled
@@ -1349,7 +1369,7 @@
         mCurrentAuthSession.onCookieReceived(cookie);
     }
 
-    private void handleAuthenticate(IBinder token, long operationId, int userId,
+    private void handleAuthenticate(IBinder token, long requestId, long operationId, int userId,
             IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo) {
         mHandler.post(() -> {
             try {
@@ -1360,7 +1380,8 @@
                 final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
 
                 Slog.d(TAG, "handleAuthenticate: modality(" + preAuthStatus.first
-                        + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo);
+                        + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo
+                        + " requestId: " + requestId);
 
                 if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
                     // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
@@ -1372,8 +1393,8 @@
                         promptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL);
                     }
 
-                    authenticateInternal(token, operationId, userId, receiver, opPackageName,
-                            promptInfo, preAuthInfo);
+                    authenticateInternal(token, requestId, operationId, userId, receiver,
+                            opPackageName, promptInfo, preAuthInfo);
                 } else {
                     receiver.onError(preAuthStatus.first /* modality */,
                             preAuthStatus.second /* errorCode */,
@@ -1394,7 +1415,7 @@
      * Note that this path is NOT invoked when the BiometricPrompt "Try again" button is pressed.
      * In that case, see {@link #handleOnTryAgainPressed()}.
      */
-    private void authenticateInternal(IBinder token, long operationId, int userId,
+    private void authenticateInternal(IBinder token, long requestId, long operationId, int userId,
             IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo,
             PreAuthInfo preAuthInfo) {
         Slog.d(TAG, "Creating authSession with authRequest: " + preAuthInfo);
@@ -1412,9 +1433,9 @@
 
         final boolean debugEnabled = mInjector.isDebugEnabled(getContext(), userId);
         mCurrentAuthSession = new AuthSession(getContext(), mStatusBarService, mSysuiReceiver,
-                mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, token, operationId, userId,
-                mBiometricSensorReceiver, receiver, opPackageName, promptInfo, debugEnabled,
-                mInjector.getFingerprintSensorProperties(getContext()));
+                mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, token, requestId,
+                operationId, userId, mBiometricSensorReceiver, receiver, opPackageName, promptInfo,
+                debugEnabled, mInjector.getFingerprintSensorProperties(getContext()));
         try {
             mCurrentAuthSession.goToInitialState();
         } catch (RemoteException e) {
@@ -1422,11 +1443,21 @@
         }
     }
 
-    private void handleCancelAuthentication() {
+    private void handleCancelAuthentication(long requestId) {
         if (mCurrentAuthSession == null) {
             Slog.e(TAG, "handleCancelAuthentication: AuthSession is null");
             return;
         }
+        if (mCurrentAuthSession.getRequestId() != requestId) {
+            // TODO: actually cancel the operation
+            // This can happen if the operation has been queued, but is cancelled before
+            // it reaches the head of the scheduler. Consider it a programming error for now
+            // and ignore it.
+            Slog.e(TAG, "handleCancelAuthentication: AuthSession mismatch current requestId: "
+                    + mCurrentAuthSession.getRequestId() + " cancel for: " + requestId
+                    + " (ignoring cancellation)");
+            return;
+        }
 
         final boolean finished = mCurrentAuthSession.onCancelAuthSession(false /* force */);
         if (finished) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 3eb6f4a..9764a16 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -26,6 +26,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.NoSuchElementException;
 
 /**
@@ -70,26 +72,32 @@
     }
 
     /** Holder for wrapping multiple handlers into a single Callback. */
-    protected static class CompositeCallback implements Callback {
+    public static class CompositeCallback implements Callback {
         @NonNull
-        private final Callback[] mCallbacks;
+        private final List<Callback> mCallbacks;
 
         public CompositeCallback(@NonNull Callback... callbacks) {
-            mCallbacks = callbacks;
+            mCallbacks = new ArrayList<>();
+
+            for (Callback callback : callbacks) {
+                if (callback != null) {
+                    mCallbacks.add(callback);
+                }
+            }
         }
 
         @Override
         public final void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-            for (int i = 0; i < mCallbacks.length; i++) {
-                mCallbacks[i].onClientStarted(clientMonitor);
+            for (int i = 0; i < mCallbacks.size(); i++) {
+                mCallbacks.get(i).onClientStarted(clientMonitor);
             }
         }
 
         @Override
         public final void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                 boolean success) {
-            for (int i = mCallbacks.length - 1; i >= 0; i--) {
-                mCallbacks[i].onClientFinished(clientMonitor, success);
+            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+                mCallbacks.get(i).onClientFinished(clientMonitor, success);
             }
         }
     }
@@ -101,6 +109,7 @@
     private final int mSensorId; // sensorId as configured by the framework
 
     @Nullable private IBinder mToken;
+    private long mRequestId;
     @Nullable private ClientMonitorCallbackConverter mListener;
     // Currently only used for authentication client. The cookie generated by BiometricService
     // is never 0.
@@ -154,6 +163,7 @@
         mSequentialId = sCount++;
         mContext = context;
         mToken = token;
+        mRequestId = -1;
         mListener = listener;
         mTargetUserId = userId;
         mOwner = owner;
@@ -254,10 +264,33 @@
         return mToken;
     }
 
-    public final int getSensorId() {
+    public int getSensorId() {
         return mSensorId;
     }
 
+    /** Unique request id. */
+    public final long getRequestId() {
+        return mRequestId;
+    }
+
+    /** If a unique id has been set via {@link #setRequestId(long)} */
+    public final boolean hasRequestId() {
+        return mRequestId > 0;
+    }
+
+    /**
+     * A unique identifier used to tie this operation to a request (i.e an API invocation).
+     *
+     * Subclasses should not call this method if this operation does not have a direct
+     * correspondence to a request and {@link #hasRequestId()} will return false.
+     */
+    protected final void setRequestId(long id) {
+        if (id <= 0) {
+            throw new IllegalArgumentException("request id must be positive");
+        }
+        mRequestId = id;
+    }
+
     @VisibleForTesting
     public Callback getCallback() {
         return mCallback;
@@ -270,6 +303,7 @@
                 + ", proto=" + getProtoEnum()
                 + ", owner=" + getOwnerString()
                 + ", cookie=" + getCookie()
+                + ", requestId=" + getRequestId()
                 + ", userId=" + getTargetUserId() + "}";
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index feb9e2a..361ec40 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -643,22 +643,18 @@
     /**
      * Requests to cancel authentication or detection.
      * @param token from the caller, should match the token passed in when requesting authentication
+     * @param requestId the id returned when requesting authentication
      */
-    public void cancelAuthenticationOrDetection(IBinder token) {
-        if (mCurrentOperation == null) {
-            Slog.e(getTag(), "Unable to cancel authentication, null operation");
-            return;
-        }
-        final boolean isCorrectClient = isAuthenticationOrDetectionOperation(mCurrentOperation);
-        final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
+    public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
+        Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId
+                + " current: " + mCurrentOperation
+                + " stack size: " + mPendingOperations.size());
 
-        Slog.d(getTag(), "cancelAuthenticationOrDetection, isCorrectClient: " + isCorrectClient
-                + ", tokenMatches: " + tokenMatches);
-
-        if (isCorrectClient && tokenMatches) {
+        if (mCurrentOperation != null
+                && canCancelAuthOperation(mCurrentOperation, token, requestId)) {
             Slog.d(getTag(), "Cancelling: " + mCurrentOperation);
             cancelInternal(mCurrentOperation);
-        } else if (!isCorrectClient) {
+        } else {
             // Look through the current queue for all authentication clients for the specified
             // token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking
             // all of them, instead of just the first one, since the API surface currently doesn't
@@ -666,8 +662,7 @@
             // process. However, this generally does not happen anyway, and would be a class of
             // bugs on its own.
             for (Operation operation : mPendingOperations) {
-                if (isAuthenticationOrDetectionOperation(operation)
-                        && operation.mClientMonitor.getToken() == token) {
+                if (canCancelAuthOperation(operation, token, requestId)) {
                     Slog.d(getTag(), "Marking " + operation
                             + " as STATE_WAITING_IN_QUEUE_CANCELING");
                     operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
@@ -676,10 +671,26 @@
         }
     }
 
-    private boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
-        final boolean isAuthentication = operation.mClientMonitor
-                instanceof AuthenticationConsumer;
-        final boolean isDetection = operation.mClientMonitor instanceof DetectionConsumer;
+    private static boolean canCancelAuthOperation(Operation operation, IBinder token,
+            long requestId) {
+        // TODO: restrict callers that can cancel without requestId (negative value)?
+        return isAuthenticationOrDetectionOperation(operation)
+                && operation.mClientMonitor.getToken() == token
+                && isMatchingRequestId(operation, requestId);
+    }
+
+    // By default, monitors are not associated with a request id to retain the original
+    // behavior (i.e. if no requestId is explicitly set then assume it matches)
+    private static boolean isMatchingRequestId(Operation operation, long requestId) {
+        return !operation.mClientMonitor.hasRequestId()
+                || operation.mClientMonitor.getRequestId() == requestId;
+    }
+
+    private static boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
+        final boolean isAuthentication =
+                operation.mClientMonitor instanceof AuthenticationConsumer;
+        final boolean isDetection =
+                operation.mClientMonitor instanceof DetectionConsumer;
         return isAuthentication || isDetection;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index a15e14b..9191b8b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -31,7 +31,7 @@
 /**
  * A class to keep track of the enrollment state for a given client.
  */
-public abstract class EnrollClient<T> extends AcquisitionClient<T> {
+public abstract class EnrollClient<T> extends AcquisitionClient<T> implements EnrollmentModifier {
 
     private static final String TAG = "Biometrics/EnrollClient";
 
@@ -40,6 +40,7 @@
     protected final BiometricUtils mBiometricUtils;
 
     private long mEnrollmentStartTimeMs;
+    private final boolean mHasEnrollmentsBeforeStarting;
 
     /**
      * @return true if the user has already enrolled the maximum number of templates.
@@ -56,6 +57,18 @@
         mBiometricUtils = utils;
         mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length);
         mTimeoutSec = timeoutSec;
+        mHasEnrollmentsBeforeStarting = hasEnrollments();
+    }
+
+    @Override
+    public boolean hasEnrollmentStateChanged() {
+        final boolean hasEnrollmentsNow = hasEnrollments();
+        return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+    }
+
+    @Override
+    public boolean hasEnrollments() {
+        return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
     }
 
     public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java b/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java
new file mode 100644
index 0000000..c2f909b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors;
+
+/**
+ * Interface for {@link BaseClientMonitor} subclasses that affect the state of enrollment.
+ */
+public interface EnrollmentModifier {
+
+    /**
+     * Callers should typically check this after
+     * {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)}
+     *
+     * @return true if the user has gone from:
+     *      1) none-enrolled --> enrolled
+     *      2) enrolled --> none-enrolled
+     *      but NOT any-enrolled --> more-enrolled
+     */
+    boolean hasEnrollmentStateChanged();
+
+    /**
+     * @return true if the user has any enrollments
+     */
+    boolean hasEnrollments();
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 282261e..579dfd6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -40,7 +40,8 @@
  * {@link #onRemoved(BiometricAuthenticator.Identifier, int)} returns true/
  */
 public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Identifier, T>
-        extends HalClientMonitor<T> implements EnumerateConsumer, RemovalConsumer {
+        extends HalClientMonitor<T> implements EnumerateConsumer, RemovalConsumer,
+        EnrollmentModifier {
 
     private static final String TAG = "Biometrics/InternalCleanupClient";
 
@@ -61,6 +62,7 @@
     private final BiometricUtils<S> mBiometricUtils;
     private final Map<Integer, Long> mAuthenticatorIds;
     private final List<S> mEnrolledList;
+    private final boolean mHasEnrollmentsBeforeStarting;
     private BaseClientMonitor mCurrentTask;
 
     private final Callback mEnumerateCallback = new Callback() {
@@ -115,6 +117,7 @@
         mBiometricUtils = utils;
         mAuthenticatorIds = authenticatorIds;
         mEnrolledList = enrolledList;
+        mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
     }
 
     private void startCleanupUnknownHalTemplates() {
@@ -166,6 +169,18 @@
     }
 
     @Override
+    public boolean hasEnrollmentStateChanged() {
+        final boolean hasEnrollmentsNow = !mBiometricUtils
+                .getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+        return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+    }
+
+    @Override
+    public boolean hasEnrollments() {
+        return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+    }
+
+    @Override
     public void onEnumerationResult(BiometricAuthenticator.Identifier identifier,
             int remaining) {
         if (!(mCurrentTask instanceof InternalEnumerateClient)) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 383efce..2a6677e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -33,12 +33,13 @@
  * A class to keep track of the remove state for a given client.
  */
 public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier, T>
-        extends HalClientMonitor<T> implements RemovalConsumer {
+        extends HalClientMonitor<T> implements RemovalConsumer, EnrollmentModifier {
 
     private static final String TAG = "Biometrics/RemovalClient";
 
     private final BiometricUtils<S> mBiometricUtils;
     private final Map<Integer, Long> mAuthenticatorIds;
+    private final boolean mHasEnrollmentsBeforeStarting;
 
     public RemovalClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
@@ -49,6 +50,7 @@
                 BiometricsProtoEnums.CLIENT_UNKNOWN);
         mBiometricUtils = utils;
         mAuthenticatorIds = authenticatorIds;
+        mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
     }
 
     @Override
@@ -91,6 +93,18 @@
     }
 
     @Override
+    public boolean hasEnrollmentStateChanged() {
+        final boolean hasEnrollmentsNow = !mBiometricUtils
+                .getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+        return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+    }
+
+    @Override
+    public boolean hasEnrollments() {
+        return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+    }
+
+    @Override
     public int getProtoEnum() {
         return BiometricsProto.CM_REMOVE;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index 0bc4f1b..b2fd46d1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -61,10 +61,11 @@
     @Override
     public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
             long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
-            String opPackageName, int cookie, boolean allowBackgroundAuthentication)
+            String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
             throws RemoteException {
         mFaceService.prepareForAuthentication(mSensorId, requireConfirmation, token, operationId,
-                userId, sensorReceiver, opPackageName, cookie, allowBackgroundAuthentication);
+                userId, sensorReceiver, opPackageName, requestId, cookie,
+                allowBackgroundAuthentication);
     }
 
     @Override
@@ -73,9 +74,9 @@
     }
 
     @Override
-    public void cancelAuthenticationFromService(IBinder token, String opPackageName)
+    public void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId)
             throws RemoteException {
-        mFaceService.cancelAuthenticationFromService(mSensorId, token, opPackageName);
+        mFaceService.cancelAuthenticationFromService(mSensorId, token, opPackageName, requestId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 12d6b08..675ee545 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -250,7 +250,7 @@
         }
 
         @Override // Binder call
-        public void authenticate(final IBinder token, final long operationId, int userId,
+        public long authenticate(final IBinder token, final long operationId, int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 boolean isKeyguardBypassEnabled) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
@@ -270,38 +270,38 @@
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for authenticate");
-                return;
+                return -1;
             }
 
-            provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
+            return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
                     0 /* cookie */,
                     new ClientMonitorCallbackConverter(receiver), opPackageName, restricted,
                     statsClient, isKeyguard, isKeyguardBypassEnabled);
         }
 
         @Override // Binder call
-        public void detectFace(final IBinder token, final int userId,
+        public long detectFace(final IBinder token, final int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
             if (!Utils.isKeyguard(getContext(), opPackageName)) {
                 Slog.w(TAG, "detectFace called from non-sysui package: " + opPackageName);
-                return;
+                return -1;
             }
 
             if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
                 // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
                 // ever be invoked when the user is encrypted or lockdown.
                 Slog.e(TAG, "detectFace invoked when user is not encrypted or lockdown");
-                return;
+                return -1;
             }
 
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for detectFace");
-                return;
+                return -1;
             }
 
-            provider.second.scheduleFaceDetect(provider.first, token, userId,
+            return provider.second.scheduleFaceDetect(provider.first, token, userId,
                     new ClientMonitorCallbackConverter(receiver), opPackageName,
                     BiometricsProtoEnums.CLIENT_KEYGUARD);
         }
@@ -309,8 +309,8 @@
         @Override // Binder call
         public void prepareForAuthentication(int sensorId, boolean requireConfirmation,
                 IBinder token, long operationId, int userId,
-                IBiometricSensorReceiver sensorReceiver, String opPackageName, int cookie,
-                boolean allowBackgroundAuthentication) {
+                IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
+                int cookie, boolean allowBackgroundAuthentication) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
 
             final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -322,9 +322,9 @@
             final boolean isKeyguardBypassEnabled = false; // only valid for keyguard clients
             final boolean restricted = true; // BiometricPrompt is always restricted
             provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie,
-                    new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, restricted,
-                    BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, allowBackgroundAuthentication,
-                    isKeyguardBypassEnabled);
+                    new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, requestId,
+                    restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
+                    allowBackgroundAuthentication, isKeyguardBypassEnabled);
         }
 
         @Override // Binder call
@@ -341,7 +341,8 @@
         }
 
         @Override // Binder call
-        public void cancelAuthentication(final IBinder token, final String opPackageName) {
+        public void cancelAuthentication(final IBinder token, final String opPackageName,
+                final long requestId) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
 
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -350,11 +351,12 @@
                 return;
             }
 
-            provider.second.cancelAuthentication(provider.first, token);
+            provider.second.cancelAuthentication(provider.first, token, requestId);
         }
 
         @Override // Binder call
-        public void cancelFaceDetect(final IBinder token, final String opPackageName) {
+        public void cancelFaceDetect(final IBinder token, final String opPackageName,
+                final long requestId) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
             if (!Utils.isKeyguard(getContext(), opPackageName)) {
                 Slog.w(TAG, "cancelFaceDetect called from non-sysui package: "
@@ -368,12 +370,12 @@
                 return;
             }
 
-            provider.second.cancelFaceDetect(provider.first, token);
+            provider.second.cancelFaceDetect(provider.first, token, requestId);
         }
 
         @Override // Binder call
         public void cancelAuthenticationFromService(int sensorId, final IBinder token,
-                final String opPackageName) {
+                final String opPackageName, final long requestId) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
 
             final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -382,7 +384,7 @@
                 return;
             }
 
-            provider.cancelAuthentication(sensorId, token);
+            provider.cancelAuthentication(sensorId, token, requestId);
         }
 
         @Override // Binder call
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 93ab1b6..e099ba3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -101,18 +101,23 @@
 
     void cancelEnrollment(int sensorId, @NonNull IBinder token);
 
-    void scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
+    long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
             @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
             int statsClient);
 
-    void cancelFaceDetect(int sensorId, @NonNull IBinder token);
+    void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId);
 
-    void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+    long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull String opPackageName, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled);
 
-    void cancelAuthentication(int sensorId, @NonNull IBinder token);
+    void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+            int cookie, @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+            boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled);
+
+    void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId);
 
     void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId,
             @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index d66a279..cbceba6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -65,7 +65,8 @@
     @FaceManager.FaceAcquired private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
 
     FaceAuthenticationClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+            @NonNull LazyDaemon<ISession> lazyDaemon,
+            @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
             boolean isStrongBiometric, int statsClient, @NonNull UsageStats usageStats,
@@ -76,6 +77,7 @@
                 BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
                 lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
                 isKeyguardBypassEnabled);
+        setRequestId(requestId);
         mUsageStats = usageStats;
         mLockoutCache = lockoutCache;
         mNotificationManager = context.getSystemService(NotificationManager.class);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 1e73ac5..2ef0911 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -43,11 +43,13 @@
     @Nullable private ICancellationSignal mCancellationSignal;
 
     public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
-            @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
+            @NonNull IBinder token, long requestId,
+            @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId, boolean isStrongBiometric, int statsClient) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
                 true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FACE,
                 BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+        setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 718b9da..4bae775 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -65,6 +65,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * Provider for a single instance of the {@link IFace} HAL.
@@ -83,6 +84,8 @@
     @NonNull private final UsageStats mUsageStats;
     @NonNull private final ActivityTaskManager mActivityTaskManager;
     @NonNull private final BiometricTaskStackListener mTaskStackListener;
+    // for requests that do not use biometric prompt
+    @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
 
     @Nullable private IFace mDaemon;
 
@@ -110,8 +113,8 @@
                                 && !client.isAlreadyDone()) {
                             Slog.e(getTag(), "Stopping background authentication, top: "
                                     + topPackage + " currentClient: " + client);
-                            mSensors.valueAt(i).getScheduler()
-                                    .cancelAuthenticationOrDetection(client.getToken());
+                            mSensors.valueAt(i).getScheduler().cancelAuthenticationOrDetection(
+                                    client.getToken(), client.getRequestId());
                         }
                     }
                 }
@@ -356,34 +359,39 @@
     }
 
     @Override
-    public void scheduleFaceDetect(int sensorId, @NonNull IBinder token,
+    public long scheduleFaceDetect(int sensorId, @NonNull IBinder token,
             int userId, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull String opPackageName, int statsClient) {
+        final long id = mRequestCounter.incrementAndGet();
+
         mHandler.post(() -> {
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FaceDetectClient client = new FaceDetectClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token, callback, userId, opPackageName,
+                    mSensors.get(sensorId).getLazySession(),
+                    token, id, callback, userId, opPackageName,
                     sensorId, isStrongBiometric, statsClient);
             scheduleForSensor(sensorId, client);
         });
+
+        return id;
     }
 
     @Override
-    public void cancelFaceDetect(int sensorId, @NonNull IBinder token) {
+    public void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId) {
         mHandler.post(() -> mSensors.get(sensorId).getScheduler()
-                .cancelAuthenticationOrDetection(token));
+                .cancelAuthenticationOrDetection(token, requestId));
     }
 
     @Override
     public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
             int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull String opPackageName, boolean restricted, int statsClient,
+            @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
         mHandler.post(() -> {
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FaceAuthenticationClient client = new FaceAuthenticationClient(
-                    mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
-                    operationId, restricted, opPackageName, cookie,
+                    mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
+                    userId, operationId, restricted, opPackageName, cookie,
                     false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
                     mUsageStats, mSensors.get(sensorId).getLockoutCache(),
                     allowBackgroundAuthentication, isKeyguardBypassEnabled);
@@ -392,9 +400,23 @@
     }
 
     @Override
-    public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
+    public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+            int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull String opPackageName, boolean restricted, int statsClient,
+            boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
+        final long id = mRequestCounter.incrementAndGet();
+
+        scheduleAuthenticate(sensorId, token, operationId, userId, cookie, callback,
+                opPackageName, id, restricted, statsClient,
+                allowBackgroundAuthentication, isKeyguardBypassEnabled);
+
+        return id;
+    }
+
+    @Override
+    public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
         mHandler.post(() -> mSensors.get(sensorId).getScheduler()
-                .cancelAuthenticationOrDetection(token));
+                .cancelAuthenticationOrDetection(token, requestId));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index d05333d..f4dcbbb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -87,6 +87,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * Supports a single instance of the {@link android.hardware.biometrics.face.V1_0} or its extended
@@ -115,6 +116,8 @@
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
     @Nullable private IBiometricsFace mDaemon;
     @NonNull private final HalResultController mHalResultController;
+    // for requests that do not use biometric prompt
+    @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     private int mCurrentUserId = UserHandle.USER_NULL;
     private final int mSensorId;
     private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
@@ -605,7 +608,7 @@
     }
 
     @Override
-    public void scheduleFaceDetect(int sensorId, @NonNull IBinder token,
+    public long scheduleFaceDetect(int sensorId, @NonNull IBinder token,
             int userId, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull String opPackageName, int statsClient) {
         throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
@@ -613,7 +616,7 @@
     }
 
     @Override
-    public void cancelFaceDetect(int sensorId, @NonNull IBinder token) {
+    public void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId) {
         throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
                 + "forget to check the supportsFaceDetection flag?");
     }
@@ -621,26 +624,38 @@
     @Override
     public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
             int userId, int cookie, @NonNull ClientMonitorCallbackConverter receiver,
-            @NonNull String opPackageName, boolean restricted, int statsClient,
+            @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
             boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
             final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
-                    mLazyDaemon, token, receiver, userId, operationId, restricted, opPackageName,
-                    cookie, false /* requireConfirmation */, mSensorId, isStrongBiometric,
-                    statsClient, mLockoutTracker, mUsageStats, allowBackgroundAuthentication,
-                    isKeyguardBypassEnabled);
+                    mLazyDaemon, token, requestId, receiver, userId, operationId, restricted,
+                    opPackageName, cookie, false /* requireConfirmation */, mSensorId,
+                    isStrongBiometric, statsClient, mLockoutTracker, mUsageStats,
+                    allowBackgroundAuthentication, isKeyguardBypassEnabled);
             mScheduler.scheduleClientMonitor(client);
         });
     }
 
     @Override
-    public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
-        mHandler.post(() -> {
-            mScheduler.cancelAuthenticationOrDetection(token);
-        });
+    public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+            int userId, int cookie, @NonNull ClientMonitorCallbackConverter receiver,
+            @NonNull String opPackageName, boolean restricted, int statsClient,
+            boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
+        final long id = mRequestCounter.incrementAndGet();
+
+        scheduleAuthenticate(sensorId, token, operationId, userId, cookie, receiver,
+                opPackageName, id, restricted, statsClient,
+                allowBackgroundAuthentication, isKeyguardBypassEnabled);
+
+        return id;
+    }
+
+    @Override
+    public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
+        mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 33950af..40f2801 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -57,7 +57,8 @@
     private int mLastAcquire;
 
     FaceAuthenticationClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
+            @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
+            @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
             boolean isStrongBiometric, int statsClient, @NonNull LockoutTracker lockoutTracker,
@@ -68,6 +69,7 @@
                 BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
                 lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
                 isKeyguardBypassEnabled);
+        setRequestId(requestId);
         mUsageStats = usageStats;
 
         final Resources resources = getContext().getResources();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index 1e59429..52d887a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -61,10 +61,10 @@
     @Override
     public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
             long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
-            String opPackageName, int cookie, boolean allowBackgroundAuthentication)
+            String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
             throws RemoteException {
         mFingerprintService.prepareForAuthentication(mSensorId, token, operationId, userId,
-                sensorReceiver, opPackageName, cookie, allowBackgroundAuthentication);
+                sensorReceiver, opPackageName, requestId, cookie, allowBackgroundAuthentication);
     }
 
     @Override
@@ -73,9 +73,10 @@
     }
 
     @Override
-    public void cancelAuthenticationFromService(IBinder token, String opPackageName)
+    public void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId)
             throws RemoteException {
-        mFingerprintService.cancelAuthenticationFromService(mSensorId, token, opPackageName);
+        mFingerprintService.cancelAuthenticationFromService(
+                mSensorId, token, opPackageName, requestId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 183fabd..f35bb7f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -35,6 +35,7 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricsProtoEnums;
@@ -62,11 +63,13 @@
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.util.EventLog;
 import android.util.Pair;
@@ -111,6 +114,7 @@
     private final FingerprintServiceWrapper mServiceWrapper;
     @NonNull private final List<ServiceProvider> mServiceProviders;
     @NonNull private final FingerprintStateCallback mFingerprintStateCallback;
+    @NonNull private final Handler mHandler;
 
     @GuardedBy("mLock")
     @NonNull private final RemoteCallbackList<IFingerprintAuthenticatorsRegisteredCallback>
@@ -125,6 +129,37 @@
      */
     public void registerFingerprintStateListener(@NonNull IFingerprintStateListener listener) {
         mFingerprintStateCallback.registerFingerprintStateListener(listener);
+        broadcastCurrentEnrollmentState(listener);
+    }
+
+    /**
+     * @param listener if non-null, notifies only this listener. if null, notifies all listeners
+     *                 in {@link FingerprintStateCallback}. This is slightly ugly, but reduces
+     *                 redundant code.
+     */
+    private void broadcastCurrentEnrollmentState(@Nullable IFingerprintStateListener listener) {
+        final UserManager um = UserManager.get(getContext());
+        synchronized (mLock) {
+            // Update the new listener with current state of all sensors
+            for (FingerprintSensorPropertiesInternal prop : mSensorProps) {
+                final ServiceProvider provider = getProviderForSensor(prop.sensorId);
+                for (UserInfo userInfo : um.getAliveUsers()) {
+                    final boolean enrolled = !provider
+                            .getEnrolledFingerprints(prop.sensorId, userInfo.id).isEmpty();
+
+                    // Defer this work and allow the loop to release the lock sooner
+                    mHandler.post(() -> {
+                        if (listener != null) {
+                            mFingerprintStateCallback.notifyFingerprintEnrollmentStateChanged(
+                                    listener, userInfo.id, prop.sensorId, enrolled);
+                        } else {
+                            mFingerprintStateCallback.notifyAllFingerprintEnrollmentStateChanged(
+                                    userInfo.id, prop.sensorId, enrolled);
+                        }
+                    });
+                }
+            }
+        }
     }
 
     /**
@@ -143,8 +178,7 @@
                 return null;
             }
 
-            return provider.createTestSession(sensorId, callback, mFingerprintStateCallback,
-                    opPackageName);
+            return provider.createTestSession(sensorId, callback, opPackageName);
         }
 
         @Override
@@ -227,7 +261,7 @@
             }
 
             provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
-                    receiver, opPackageName, enrollReason, mFingerprintStateCallback);
+                    receiver, opPackageName, enrollReason);
         }
 
         @Override // Binder call
@@ -245,7 +279,7 @@
 
         @SuppressWarnings("deprecation")
         @Override // Binder call
-        public void authenticate(final IBinder token, final long operationId,
+        public long authenticate(final IBinder token, final long operationId,
                 final int sensorId, final int userId, final IFingerprintServiceReceiver receiver,
                 final String opPackageName) {
             final int callingUid = Binder.getCallingUid();
@@ -255,7 +289,7 @@
             if (!canUseFingerprint(opPackageName, true /* requireForeground */, callingUid,
                     callingPid, callingUserId)) {
                 Slog.w(TAG, "Authenticate rejecting package: " + opPackageName);
-                return;
+                return -1;
             }
 
             // Keyguard check must be done on the caller's binder identity, since it also checks
@@ -270,7 +304,7 @@
                     // SafetyNet for b/79776455
                     EventLog.writeEvent(0x534e4554, "79776455");
                     Slog.e(TAG, "Authenticate invoked when user is encrypted or lockdown");
-                    return;
+                    return -1;
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -290,7 +324,7 @@
             }
             if (provider == null) {
                 Slog.w(TAG, "Null provider for authenticate");
-                return;
+                return -1;
             }
 
             final FingerprintSensorPropertiesInternal sensorProps =
@@ -299,18 +333,17 @@
                     && sensorProps != null && sensorProps.isAnyUdfpsType()) {
                 identity = Binder.clearCallingIdentity();
                 try {
-                    authenticateWithPrompt(operationId, sensorProps, userId, receiver);
+                    return authenticateWithPrompt(operationId, sensorProps, userId, receiver);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
-            } else {
-                provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
-                        0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
-                        restricted, statsClient, isKeyguard, mFingerprintStateCallback);
             }
+            return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
+                    0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
+                    restricted, statsClient, isKeyguard);
         }
 
-        private void authenticateWithPrompt(
+        private long authenticateWithPrompt(
                 final long operationId,
                 @NonNull final FingerprintSensorPropertiesInternal props,
                 final int userId,
@@ -387,41 +420,41 @@
                         }
                     };
 
-            biometricPrompt.authenticateUserForOperation(
+            return biometricPrompt.authenticateUserForOperation(
                     new CancellationSignal(), executor, promptCallback, userId, operationId);
         }
 
         @Override
-        public void detectFingerprint(final IBinder token, final int userId,
+        public long detectFingerprint(final IBinder token, final int userId,
                 final IFingerprintServiceReceiver receiver, final String opPackageName) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
             if (!Utils.isKeyguard(getContext(), opPackageName)) {
                 Slog.w(TAG, "detectFingerprint called from non-sysui package: " + opPackageName);
-                return;
+                return -1;
             }
 
             if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
                 // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
                 // ever be invoked when the user is encrypted or lockdown.
                 Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
-                return;
+                return -1;
             }
 
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for detectFingerprint");
-                return;
+                return -1;
             }
 
-            provider.second.scheduleFingerDetect(provider.first, token, userId,
+            return provider.second.scheduleFingerDetect(provider.first, token, userId,
                     new ClientMonitorCallbackConverter(receiver), opPackageName,
-                    BiometricsProtoEnums.CLIENT_KEYGUARD, mFingerprintStateCallback);
+                    BiometricsProtoEnums.CLIENT_KEYGUARD);
         }
 
         @Override // Binder call
         public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
                 int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
-                int cookie, boolean allowBackgroundAuthentication) {
+                long requestId, int cookie, boolean allowBackgroundAuthentication) {
             Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
 
             final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -432,9 +465,9 @@
 
             final boolean restricted = true; // BiometricPrompt is always restricted
             provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie,
-                    new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, restricted,
-                    BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, allowBackgroundAuthentication,
-                    mFingerprintStateCallback);
+                    new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, requestId,
+                    restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
+                    allowBackgroundAuthentication);
         }
 
         @Override // Binder call
@@ -452,7 +485,8 @@
 
 
         @Override // Binder call
-        public void cancelAuthentication(final IBinder token, final String opPackageName) {
+        public void cancelAuthentication(final IBinder token, final String opPackageName,
+                long requestId) {
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
             final int callingUserId = UserHandle.getCallingUserId();
@@ -469,11 +503,12 @@
                 return;
             }
 
-            provider.second.cancelAuthentication(provider.first, token);
+            provider.second.cancelAuthentication(provider.first, token, requestId);
         }
 
         @Override // Binder call
-        public void cancelFingerprintDetect(final IBinder token, final String opPackageName) {
+        public void cancelFingerprintDetect(final IBinder token, final String opPackageName,
+                final long requestId) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
             if (!Utils.isKeyguard(getContext(), opPackageName)) {
                 Slog.w(TAG, "cancelFingerprintDetect called from non-sysui package: "
@@ -489,12 +524,12 @@
                 return;
             }
 
-            provider.second.cancelAuthentication(provider.first, token);
+            provider.second.cancelAuthentication(provider.first, token, requestId);
         }
 
         @Override // Binder call
         public void cancelAuthenticationFromService(final int sensorId, final IBinder token,
-                final String opPackageName) {
+                final String opPackageName, final long requestId) {
 
             Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
 
@@ -506,7 +541,7 @@
                 return;
             }
 
-            provider.cancelAuthentication(sensorId, token);
+            provider.cancelAuthentication(sensorId, token, requestId);
         }
 
         @Override // Binder call
@@ -686,27 +721,6 @@
                     .isEmpty();
         }
 
-        @Override // Binder call
-        public boolean hasEnrolledTemplatesForAnySensor(int userId,
-                @NonNull List<FingerprintSensorPropertiesInternal> sensors,
-                @NonNull String opPackageName) {
-            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-
-            for (FingerprintSensorPropertiesInternal prop : sensors) {
-                final ServiceProvider provider = getProviderForSensor(prop.sensorId);
-                if (provider == null) {
-                    Slog.w(TAG, "Null provider for sensorId: " + prop.sensorId
-                            + ", caller: " + opPackageName);
-                    continue;
-                }
-
-                if (!provider.getEnrolledFingerprints(prop.sensorId, userId).isEmpty()) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
         public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
 
@@ -796,10 +810,12 @@
                         && Settings.Secure.getIntForUser(getContext().getContentResolver(),
                         Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */,
                         UserHandle.USER_CURRENT) != 0) {
-                    fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), hidlSensor,
+                    fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(),
+                            mFingerprintStateCallback, hidlSensor,
                             mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
                 } else {
-                    fingerprint21 = Fingerprint21.newInstance(getContext(), hidlSensor,
+                    fingerprint21 = Fingerprint21.newInstance(getContext(),
+                            mFingerprintStateCallback, hidlSensor,
                             mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
                 }
                 mServiceProviders.add(fingerprint21);
@@ -822,8 +838,9 @@
                 try {
                     final SensorProps[] props = fp.getSensorProps();
                     final FingerprintProvider provider =
-                            new FingerprintProvider(getContext(), props, instance,
-                                    mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+                            new FingerprintProvider(getContext(), mFingerprintStateCallback, props,
+                                    instance, mLockoutResetDispatcher,
+                                    mGestureAvailabilityDispatcher);
                     mServiceProviders.add(provider);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -877,6 +894,7 @@
                     }
                 }
 
+                broadcastCurrentEnrollmentState(null); // broadcasts to all listeners
                 broadcastAllAuthenticatorsRegistered();
             });
         }
@@ -974,6 +992,7 @@
         mFingerprintStateCallback = new FingerprintStateCallback();
         mAuthenticatorsRegisteredCallbacks = new RemoteCallbackList<>();
         mSensorProps = new ArrayList<>();
+        mHandler = new Handler(Looper.getMainLooper());
     }
 
     // Notifies the callbacks that all of the authenticators have been registered and removes the
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
index 5f998d8..0050a89 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
@@ -23,6 +23,7 @@
 import static android.hardware.fingerprint.FingerprintStateListener.STATE_KEYGUARD_AUTH;
 
 import android.annotation.NonNull;
+import android.content.Context;
 import android.hardware.fingerprint.FingerprintStateListener;
 import android.hardware.fingerprint.IFingerprintStateListener;
 import android.os.RemoteException;
@@ -31,6 +32,9 @@
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.EnrollClient;
+import com.android.server.biometrics.sensors.EnrollmentModifier;
+import com.android.server.biometrics.sensors.RemovalConsumer;
 import com.android.server.biometrics.sensors.fingerprint.hidl.FingerprintEnrollClient;
 
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -39,9 +43,11 @@
  * A callback for receiving notifications about changes in fingerprint state.
  */
 public class FingerprintStateCallback implements BaseClientMonitor.Callback {
-    private @FingerprintStateListener.State int mFingerprintState;
+
     @NonNull private final CopyOnWriteArrayList<IFingerprintStateListener>
-        mFingerprintStateListeners = new CopyOnWriteArrayList<>();
+            mFingerprintStateListeners = new CopyOnWriteArrayList<>();
+
+    private @FingerprintStateListener.State int mFingerprintState;
 
     public FingerprintStateCallback() {
         mFingerprintState = STATE_IDLE;
@@ -54,8 +60,9 @@
     @Override
     public void onClientStarted(@NonNull BaseClientMonitor client) {
         final int previousFingerprintState = mFingerprintState;
+
         if (client instanceof AuthenticationClient) {
-            AuthenticationClient authClient = (AuthenticationClient) client;
+            final AuthenticationClient<?> authClient = (AuthenticationClient<?>) client;
             if (authClient.isKeyguard()) {
                 mFingerprintState = STATE_KEYGUARD_AUTH;
             } else if (authClient.isBiometricPrompt()) {
@@ -70,6 +77,7 @@
                     "Other authentication client: " + Utils.getClientName(client));
             mFingerprintState = STATE_IDLE;
         }
+
         Slog.d(FingerprintService.TAG, "Fps state updated from " + previousFingerprintState
                 + " to " + mFingerprintState + ", client " + client);
         notifyFingerprintStateListeners(mFingerprintState);
@@ -81,6 +89,18 @@
         Slog.d(FingerprintService.TAG,
                 "Client finished, fps state updated to " + mFingerprintState + ", client "
                         + client);
+
+        if (client instanceof EnrollmentModifier) {
+            EnrollmentModifier enrollmentModifier = (EnrollmentModifier) client;
+            final boolean enrollmentStateChanged = enrollmentModifier.hasEnrollmentStateChanged();
+            Slog.d(FingerprintService.TAG, "Enrollment state changed: " + enrollmentStateChanged);
+            if (enrollmentStateChanged) {
+                notifyAllFingerprintEnrollmentStateChanged(client.getTargetUserId(),
+                        client.getSensorId(),
+                        enrollmentModifier.hasEnrollments());
+            }
+        }
+
         notifyFingerprintStateListeners(mFingerprintState);
     }
 
@@ -95,6 +115,32 @@
     }
 
     /**
+     * This should be invoked when:
+     *  1) Enrolled --> None-enrolled
+     *  2) None-enrolled --> enrolled
+     *  3) HAL becomes ready
+     *  4) Listener is registered
+     */
+    void notifyAllFingerprintEnrollmentStateChanged(int userId, int sensorId,
+            boolean hasEnrollments) {
+        for (IFingerprintStateListener listener : mFingerprintStateListeners) {
+            notifyFingerprintEnrollmentStateChanged(listener, userId, sensorId, hasEnrollments);
+        }
+    }
+
+    /**
+     * Notifies the listener of enrollment state changes.
+     */
+    void notifyFingerprintEnrollmentStateChanged(@NonNull IFingerprintStateListener listener,
+            int userId, int sensorId, boolean hasEnrollments) {
+        try {
+            listener.onEnrollmentsChanged(userId, sensorId, hasEnrollments);
+        } catch (RemoteException e) {
+            Slog.e(FingerprintService.TAG, "Remote exception", e);
+        }
+    }
+
+    /**
      * Enables clients to register a FingerprintStateListener. Used by FingerprintService to forward
      * updates in fingerprint sensor state to the SideFpNsEventHandler
      * @param listener
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 706ac10..1772f81 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -90,25 +90,27 @@
      */
     void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
             int userId, @NonNull IFingerprintServiceReceiver receiver,
-            @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason,
-            @NonNull FingerprintStateCallback fingerprintStateCallback);
+            @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason);
 
     void cancelEnrollment(int sensorId, @NonNull IBinder token);
 
-    void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
+    long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
             @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
-            int statsClient,
-            @NonNull FingerprintStateCallback fingerprintStateCallback);
+            int statsClient);
 
     void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+            boolean allowBackgroundAuthentication);
+
+    long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+            int cookie, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull String opPackageName, boolean restricted, int statsClient,
-            boolean allowBackgroundAuthentication,
-            @NonNull FingerprintStateCallback fingerprintStateCallback);
+            boolean allowBackgroundAuthentication);
 
     void startPreparedClient(int sensorId, int cookie);
 
-    void cancelAuthentication(int sensorId, @NonNull IBinder token);
+    void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId);
 
     void scheduleRemove(int sensorId, @NonNull IBinder token,
             @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
@@ -163,6 +165,5 @@
 
     @NonNull
     ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
-            @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull String opPackageName);
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 29f2f20..2b50b96 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -143,8 +143,7 @@
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
         mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
-                mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
-                mFingerprintStateCallback);
+                mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 37ee76a..9d911e0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -61,7 +61,8 @@
     private boolean mIsPointerDown;
 
     FingerprintAuthenticationClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+            @NonNull LazyDaemon<ISession> lazyDaemon,
+            @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
             int sensorId, boolean isStrongBiometric, int statsClient,
@@ -74,6 +75,7 @@
                 BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
                 lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
                 false /* isKeyguardBypassEnabled */);
+        setRequestId(requestId);
         mLockoutCache = lockoutCache;
         mUdfpsOverlayController = udfpsOverlayController;
         mSensorProps = sensorProps;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index c5dc449..da91cdd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -47,13 +47,15 @@
     @Nullable private ICancellationSignal mCancellationSignal;
 
     FingerprintDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
-            @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
+            @NonNull IBinder token, long requestId,
+            @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId,
             @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric,
             int statsClient) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
                 true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
                 BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+        setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
         mUdfpsOverlayController = udfpsOverlayController;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 102b074..ca83dda 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -71,6 +71,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * Provider for a single instance of the {@link IFingerprint} HAL.
@@ -81,6 +82,7 @@
     private boolean mTestHalEnabled;
 
     @NonNull private final Context mContext;
+    @NonNull private final FingerprintStateCallback mFingerprintStateCallback;
     @NonNull private final String mHalInstanceName;
     @NonNull @VisibleForTesting
     final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
@@ -88,6 +90,8 @@
     @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
     @NonNull private final ActivityTaskManager mActivityTaskManager;
     @NonNull private final BiometricTaskStackListener mTaskStackListener;
+    // for requests that do not use biometric prompt
+    @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
 
     @Nullable private IFingerprint mDaemon;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@@ -118,8 +122,8 @@
                                 && !client.isAlreadyDone()) {
                             Slog.e(getTag(), "Stopping background authentication, top: "
                                     + topPackage + " currentClient: " + client);
-                            mSensors.valueAt(i).getScheduler()
-                                    .cancelAuthenticationOrDetection(client.getToken());
+                            mSensors.valueAt(i).getScheduler().cancelAuthenticationOrDetection(
+                                    client.getToken(), client.getRequestId());
                         }
                     }
                 }
@@ -127,10 +131,13 @@
         }
     }
 
-    public FingerprintProvider(@NonNull Context context, @NonNull SensorProps[] props,
-            @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+    public FingerprintProvider(@NonNull Context context,
+            @NonNull FingerprintStateCallback fingerprintStateCallback,
+            @NonNull SensorProps[] props, @NonNull String halInstanceName,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
         mContext = context;
+        mFingerprintStateCallback = fingerprintStateCallback;
         mHalInstanceName = halInstanceName;
         mSensors = new SparseArray<>();
         mHandler = new Handler(Looper.getMainLooper());
@@ -332,8 +339,7 @@
     public void scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId,
             @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
-            @FingerprintManager.EnrollReason int enrollReason,
-            @NonNull FingerprintStateCallback fingerprintStateCallback) {
+            @FingerprintManager.EnrollReason int enrollReason) {
         mHandler.post(() -> {
             final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
                     .maxEnrollmentsPerUser;
@@ -347,13 +353,13 @@
 
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                    fingerprintStateCallback.onClientStarted(clientMonitor);
+                    mFingerprintStateCallback.onClientStarted(clientMonitor);
                 }
 
                 @Override
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                         boolean success) {
-                    fingerprintStateCallback.onClientFinished(clientMonitor, success);
+                    mFingerprintStateCallback.onClientFinished(clientMonitor, success);
                     if (success) {
                         scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
                         scheduleInvalidationRequest(sensorId, userId);
@@ -369,48 +375,62 @@
     }
 
     @Override
-    public void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
+    public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
             @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
-            int statsClient,
-            @NonNull FingerprintStateCallback fingerprintStateCallback) {
+            int statsClient) {
+        final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token, callback, userId,
+                    mSensors.get(sensorId).getLazySession(), token, id, callback, userId,
                     opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
                     statsClient);
-            scheduleForSensor(sensorId, client, fingerprintStateCallback);
+            scheduleForSensor(sensorId, client, mFingerprintStateCallback);
         });
+
+        return id;
     }
 
     @Override
     public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
             int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
-            @NonNull String opPackageName, boolean restricted, int statsClient,
-            boolean allowBackgroundAuthentication,
-            @NonNull FingerprintStateCallback fingerprintStateCallback) {
+            @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+            boolean allowBackgroundAuthentication) {
         mHandler.post(() -> {
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
-                    mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
-                    operationId, restricted, opPackageName, cookie,
+                    mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
+                    userId, operationId, restricted, opPackageName, cookie,
                     false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
                     mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
                     mUdfpsOverlayController, allowBackgroundAuthentication,
                     mSensors.get(sensorId).getSensorProperties());
-            scheduleForSensor(sensorId, client, fingerprintStateCallback);
+            scheduleForSensor(sensorId, client, mFingerprintStateCallback);
         });
     }
 
     @Override
+    public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+            int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
+            @NonNull String opPackageName, boolean restricted, int statsClient,
+            boolean allowBackgroundAuthentication) {
+        final long id = mRequestCounter.incrementAndGet();
+
+        scheduleAuthenticate(sensorId, token, operationId, userId, cookie, callback,
+                opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
+
+        return id;
+    }
+
+    @Override
     public void startPreparedClient(int sensorId, int cookie) {
         mHandler.post(() -> mSensors.get(sensorId).getScheduler().startPreparedClient(cookie));
     }
 
     @Override
-    public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
+    public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
         mHandler.post(() -> mSensors.get(sensorId).getScheduler()
-                .cancelAuthenticationOrDetection(token));
+                .cancelAuthenticationOrDetection(token, requestId));
     }
 
     @Override
@@ -444,7 +464,7 @@
                     new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                     mSensors.get(sensorId).getAuthenticatorIds());
-            scheduleForSensor(sensorId, client);
+            scheduleForSensor(sensorId, client, mFingerprintStateCallback);
         });
     }
 
@@ -459,7 +479,8 @@
                             mContext.getOpPackageName(), sensorId, enrolledList,
                             FingerprintUtils.getInstance(sensorId),
                             mSensors.get(sensorId).getAuthenticatorIds());
-            scheduleForSensor(sensorId, client, callback);
+            scheduleForSensor(sensorId, client, new BaseClientMonitor.CompositeCallback(callback,
+                    mFingerprintStateCallback));
         });
     }
 
@@ -604,9 +625,8 @@
     @NonNull
     @Override
     public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
-            @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull String opPackageName) {
-        return mSensors.get(sensorId).createTestSession(callback, fingerprintStateCallback);
+        return mSensors.get(sensorId).createTestSession(callback, mFingerprintStateCallback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index c00daff..79c6b1b3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -143,8 +143,7 @@
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
         mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
-                mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
-                mFingerprintStateCallback);
+                mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 2f5b5c7..d2882aa4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -88,6 +88,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * Supports a single instance of the {@link android.hardware.biometrics.fingerprint.V2_1} or
@@ -101,6 +102,7 @@
     private boolean mTestHalEnabled;
 
     final Context mContext;
+    @NonNull private final FingerprintStateCallback mFingerprintStateCallback;
     private final ActivityTaskManager mActivityTaskManager;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
     private final BiometricScheduler mScheduler;
@@ -115,6 +117,8 @@
     @NonNull private final HalResultController mHalResultController;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
+    // for requests that do not use biometric prompt
+    @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     private int mCurrentUserId = UserHandle.USER_NULL;
     private final boolean mIsUdfps;
     private final int mSensorId;
@@ -142,7 +146,8 @@
                             && !client.isAlreadyDone()) {
                         Slog.e(TAG, "Stopping background authentication, top: "
                                 + topPackage + " currentClient: " + client);
-                        mScheduler.cancelAuthenticationOrDetection(client.getToken());
+                        mScheduler.cancelAuthenticationOrDetection(
+                                client.getToken(), client.getRequestId());
                     }
                 }
             });
@@ -313,11 +318,13 @@
     }
 
     Fingerprint21(@NonNull Context context,
+            @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull BiometricScheduler scheduler, @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull HalResultController controller) {
         mContext = context;
+        mFingerprintStateCallback = fingerprintStateCallback;
 
         mSensorProperties = sensorProps;
         mSensorId = sensorProps.sensorId;
@@ -347,6 +354,7 @@
     }
 
     public static Fingerprint21 newInstance(@NonNull Context context,
+            @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
@@ -358,8 +366,8 @@
         final HalResultController controller = new HalResultController(sensorProps.sensorId,
                 context, handler,
                 scheduler);
-        return new Fingerprint21(context, sensorProps, scheduler, handler, lockoutResetDispatcher,
-                controller);
+        return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+                lockoutResetDispatcher, controller);
     }
 
     @Override
@@ -553,8 +561,7 @@
     public void scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId,
             @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
-            @FingerprintManager.EnrollReason int enrollReason,
-            @NonNull FingerprintStateCallback fingerprintStateCallback) {
+            @FingerprintManager.EnrollReason int enrollReason) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -566,13 +573,13 @@
             mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                    fingerprintStateCallback.onClientStarted(clientMonitor);
+                    mFingerprintStateCallback.onClientStarted(clientMonitor);
                 }
 
                 @Override
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                         boolean success) {
-                    fingerprintStateCallback.onClientFinished(clientMonitor, success);
+                    mFingerprintStateCallback.onClientFinished(clientMonitor, success);
                     if (success) {
                         // Update authenticatorIds
                         scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
@@ -591,51 +598,65 @@
     }
 
     @Override
-    public void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
+    public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
             @NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
-            int statsClient,
-            @NonNull FingerprintStateCallback fingerprintStateCallback) {
+            int statsClient) {
+        final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
-                    mLazyDaemon, token, listener, userId, opPackageName,
+                    mLazyDaemon, token, id, listener, userId, opPackageName,
                     mSensorProperties.sensorId, mUdfpsOverlayController, isStrongBiometric,
                     statsClient);
-            mScheduler.scheduleClientMonitor(client, fingerprintStateCallback);
+            mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
         });
+
+        return id;
     }
 
     @Override
     public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
             int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
-            @NonNull String opPackageName, boolean restricted, int statsClient,
-            boolean allowBackgroundAuthentication,
-            @NonNull FingerprintStateCallback fingerprintStateCallback) {
+            @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+            boolean allowBackgroundAuthentication) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
-                    mContext, mLazyDaemon, token, listener, userId, operationId, restricted,
-                    opPackageName, cookie, false /* requireConfirmation */,
+                    mContext, mLazyDaemon, token, requestId, listener, userId, operationId,
+                    restricted, opPackageName, cookie, false /* requireConfirmation */,
                     mSensorProperties.sensorId, isStrongBiometric, statsClient,
                     mTaskStackListener, mLockoutTracker, mUdfpsOverlayController,
                     allowBackgroundAuthentication, mSensorProperties);
-            mScheduler.scheduleClientMonitor(client, fingerprintStateCallback);
+            mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
         });
     }
 
     @Override
+    public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+            int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
+            @NonNull String opPackageName, boolean restricted, int statsClient,
+            boolean allowBackgroundAuthentication) {
+        final long id = mRequestCounter.incrementAndGet();
+
+        scheduleAuthenticate(sensorId, token, operationId, userId, cookie, listener,
+                opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
+
+        return id;
+    }
+
+    @Override
     public void startPreparedClient(int sensorId, int cookie) {
         mHandler.post(() -> mScheduler.startPreparedClient(cookie));
     }
 
     @Override
-    public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
+    public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
         Slog.d(TAG, "cancelAuthentication, sensorId: " + sensorId);
-        mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token));
+        mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
     }
 
     @Override
@@ -649,7 +670,7 @@
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
                     userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
                     mSensorProperties.sensorId, mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client);
+            mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
         });
     }
 
@@ -666,7 +687,7 @@
                     0 /* fingerprintId */, userId, opPackageName,
                     FingerprintUtils.getLegacyInstance(mSensorId),
                     mSensorProperties.sensorId, mAuthenticatorIds);
-            mScheduler.scheduleClientMonitor(client);
+            mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
         });
     }
 
@@ -688,7 +709,8 @@
     @Override
     public void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable BaseClientMonitor.Callback callback) {
-        scheduleInternalCleanup(userId, callback);
+        scheduleInternalCleanup(userId, new BaseClientMonitor.CompositeCallback(callback,
+                mFingerprintStateCallback));
     }
 
     @Override
@@ -896,9 +918,8 @@
     @NonNull
     @Override
     public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
-            @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull String opPackageName) {
         return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback,
-                fingerprintStateCallback, this, mHalResultController);
+                mFingerprintStateCallback, this, mHalResultController);
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 24ce867..79ad8e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -26,6 +26,7 @@
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintStateListener;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Handler;
 import android.os.IBinder;
@@ -42,6 +43,7 @@
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import java.util.ArrayList;
@@ -270,6 +272,7 @@
     }
 
     public static Fingerprint21UdfpsMock newInstance(@NonNull Context context,
+            @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
@@ -280,8 +283,8 @@
                 new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher);
         final MockHalResultController controller =
                 new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
-        return new Fingerprint21UdfpsMock(context, sensorProps, scheduler, handler,
-                lockoutResetDispatcher, controller);
+        return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,
+                handler, lockoutResetDispatcher, controller);
     }
 
     private static abstract class FakeFingerRunnable implements Runnable {
@@ -400,17 +403,19 @@
             // internal preemption logic is not run.
             mFingerprint21.scheduleAuthenticate(mFingerprint21.mSensorProperties.sensorId, token,
                     operationId, user, cookie, listener, opPackageName, restricted, statsClient,
-                    isKeyguard, null /* fingerprintStateCallback */);
+                    isKeyguard);
         }
     }
 
     private Fingerprint21UdfpsMock(@NonNull Context context,
+            @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull TestableBiometricScheduler scheduler,
             @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull MockHalResultController controller) {
-        super(context, sensorProps, scheduler, handler, lockoutResetDispatcher, controller);
+        super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+                lockoutResetDispatcher, controller);
         mScheduler = scheduler;
         mScheduler.init(this);
         mHandler = handler;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 5060744..7d95ec0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -59,7 +59,8 @@
     private boolean mIsPointerDown;
 
     FingerprintAuthenticationClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+            @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon,
+            @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
             int sensorId, boolean isStrongBiometric, int statsClient,
@@ -73,6 +74,7 @@
                 BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
                 lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
                 false /* isKeyguardBypassEnabled */);
+        setRequestId(requestId);
         mLockoutFrameworkImpl = lockoutTracker;
         mUdfpsOverlayController = udfpsOverlayController;
         mSensorProps = sensorProps;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 8e73ee6b..147a206 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -52,13 +52,15 @@
     private boolean mIsPointerDown;
 
     public FingerprintDetectClient(@NonNull Context context,
-            @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+            @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon,
+            @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
             int sensorId, @Nullable IUdfpsOverlayController udfpsOverlayController,
             boolean isStrongBiometric, int statsClient) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
                 true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
                 BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+        setRequestId(requestId);
         mUdfpsOverlayController = udfpsOverlayController;
         mIsStrongBiometric = isStrongBiometric;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index 4918185..5c0c362 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -59,7 +59,7 @@
     @Override
     public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
             long sessionId, int userId, IBiometricSensorReceiver sensorReceiver,
-            String opPackageName, int cookie, boolean allowBackgroundAuthentication)
+            String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
             throws RemoteException {
     }
 
@@ -68,7 +68,7 @@
     }
 
     @Override
-    public void cancelAuthenticationFromService(IBinder token, String opPackageName)
+    public void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId)
             throws RemoteException {
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 4c9d0f2..2ae5cbb 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -590,7 +590,7 @@
             newIndex = i - newStart;
             final float newBacklightVal;
             final float newNitsVal;
-            isLastValue = mRawBacklight[i] > mBacklightMaximum
+            isLastValue = mRawBacklight[i] >= mBacklightMaximum
                     || i >= mRawBacklight.length - 1;
             // Clamp beginning and end to valid backlight values.
             if (newIndex == 0) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index afd1889..c220043 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -192,7 +192,9 @@
 
     private static final String PROP_DEFAULT_DISPLAY_TOP_INSET = "persist.sys.displayinset.top";
     private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000;
-    private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.1f;
+    // This value needs to be in sync with the threshold
+    // in RefreshRateConfigs::getFrameRateDivider.
+    private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.0009f;
 
     private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
     private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2;
@@ -812,7 +814,7 @@
 
         // Override the refresh rate only if it is a divider of the current
         // refresh rate. This calculation needs to be in sync with the native code
-        // in RefreshRateConfigs::getRefreshRateDividerForUid
+        // in RefreshRateConfigs::getFrameRateDivider
         Display.Mode currentMode = info.getMode();
         float numPeriods = currentMode.getRefreshRate() / frameRateHz;
         float numPeriodsRound = Math.round(numPeriods);
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index d66d7ee..5797b06 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -36,9 +36,14 @@
 import android.hardware.fingerprint.IUdfpsHbmListener;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.Temperature;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -108,6 +113,7 @@
     private final UdfpsObserver mUdfpsObserver;
     private final SensorObserver mSensorObserver;
     private final HbmObserver mHbmObserver;
+    private final SkinThermalStatusObserver mSkinThermalStatusObserver;
     private final DeviceConfigInterface mDeviceConfig;
     private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
 
@@ -156,6 +162,7 @@
         };
         mSensorObserver = new SensorObserver(context, ballotBox, injector);
         mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler());
+        mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, ballotBox);
         mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
         mDeviceConfig = injector.getDeviceConfig();
         mAlwaysRespectAppRequest = false;
@@ -174,6 +181,7 @@
         mBrightnessObserver.observe(sensorManager);
         mSensorObserver.observe();
         mHbmObserver.observe();
+        mSkinThermalStatusObserver.observe();
         synchronized (mLock) {
             // We may have a listener already registered before the call to start, so go ahead and
             // notify them to pick up our newly initialized state.
@@ -606,6 +614,7 @@
             mUdfpsObserver.dumpLocked(pw);
             mSensorObserver.dumpLocked(pw);
             mHbmObserver.dumpLocked(pw);
+            mSkinThermalStatusObserver.dumpLocked(pw);
         }
     }
 
@@ -714,7 +723,6 @@
         return mUdfpsObserver;
     }
 
-
     @VisibleForTesting
     DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings(
             float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
@@ -950,16 +958,19 @@
         // user seeing the display flickering when the switches occur.
         public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 8;
 
+        // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
+        public static final int PRIORITY_SKIN_TEMPERATURE = 9;
+
         // High-brightness-mode may need a specific range of refresh-rates to function properly.
-        public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 9;
+        public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 10;
 
         // The proximity sensor needs the refresh rate to be locked in order to function, so this is
         // set to a high priority.
-        public static final int PRIORITY_PROXIMITY = 10;
+        public static final int PRIORITY_PROXIMITY = 11;
 
         // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
         // to function, so this needs to be the highest priority of all votes.
-        public static final int PRIORITY_UDFPS = 11;
+        public static final int PRIORITY_UDFPS = 12;
 
         // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
         // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
@@ -1054,6 +1065,8 @@
                     return "PRIORITY_PROXIMITY";
                 case PRIORITY_LOW_POWER_MODE:
                     return "PRIORITY_LOW_POWER_MODE";
+                case PRIORITY_SKIN_TEMPERATURE:
+                    return "PRIORITY_SKIN_TEMPERATURE";
                 case PRIORITY_UDFPS:
                     return "PRIORITY_UDFPS";
                 case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
@@ -2309,6 +2322,52 @@
         }
     }
 
+    private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
+        private final BallotBox mBallotBox;
+        private final Injector mInjector;
+
+        private @Temperature.ThrottlingStatus int mStatus = -1;
+
+        SkinThermalStatusObserver(Injector injector, BallotBox ballotBox) {
+            mInjector = injector;
+            mBallotBox = ballotBox;
+        }
+
+        @Override
+        public void notifyThrottling(Temperature temp) {
+            mStatus = temp.getStatus();
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "New thermal throttling status "
+                        + ", current thermal status = " + mStatus);
+            }
+            final Vote vote;
+            if (mStatus >= Temperature.THROTTLING_CRITICAL) {
+                vote = Vote.forRefreshRates(0f, 60f);
+            } else {
+                vote = null;
+            }
+            mBallotBox.vote(GLOBAL_ID, Vote.PRIORITY_SKIN_TEMPERATURE, vote);
+        }
+
+        public void observe() {
+            IThermalService thermalService = mInjector.getThermalService();
+            if (thermalService == null) {
+                Slog.w(TAG, "Could not observe thermal status. Service not available");
+                return;
+            }
+            try {
+                thermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to register thermal status listener", e);
+            }
+        }
+
+        void dumpLocked(PrintWriter writer) {
+            writer.println("  SkinThermalStatusObserver:");
+            writer.println("    mStatus: " + mStatus);
+        }
+    }
+
     private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener {
         public DeviceConfigDisplaySettings() {
         }
@@ -2470,6 +2529,8 @@
         BrightnessInfo getBrightnessInfo(int displayId);
 
         boolean isDozeState(Display d);
+
+        IThermalService getThermalService();
     }
 
     @VisibleForTesting
@@ -2530,6 +2591,12 @@
             return Display.isDozeState(d.getState());
         }
 
+        @Override
+        public IThermalService getThermalService() {
+            return IThermalService.Stub.asInterface(
+                    ServiceManager.getService(Context.THERMAL_SERVICE));
+        }
+
         private DisplayManager getDisplayManager() {
             if (mDisplayManager == null) {
                 mDisplayManager = mContext.getSystemService(DisplayManager.class);
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index f3dcfbb..9956da2 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.server.location.gnss;
 
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
 import static android.location.provider.ProviderProperties.ACCURACY_FINE;
 import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH;
 
@@ -43,6 +44,8 @@
 import static com.android.server.location.gnss.hal.GnssNative.GNSS_POSITION_MODE_STANDALONE;
 import static com.android.server.location.gnss.hal.GnssNative.GNSS_POSITION_RECURRENCE_PERIODIC;
 
+import static java.lang.Math.abs;
+import static java.lang.Math.max;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
 import android.app.AlarmManager;
@@ -85,6 +88,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
+import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.TimeUtils;
 
@@ -103,7 +107,9 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
@@ -156,6 +162,10 @@
     private static final long LOCATION_UPDATE_DURATION_MILLIS = 10 * 1000;
     // Update duration extension multiplier for emergency REQUEST_LOCATION.
     private static final int EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER = 3;
+    // maximum length gnss batching may go for (1 day)
+    private static final int MIN_BATCH_INTERVAL_MS = (int) DateUtils.SECOND_IN_MILLIS;
+    private static final long MAX_BATCH_LENGTH_MS = DateUtils.DAY_IN_MILLIS;
+    private static final long MAX_BATCH_TIMESTAMP_DELTA_MS = 500;
 
     // Threadsafe class to hold stats reported in the Extras Bundle
     private static class LocationExtras {
@@ -245,6 +255,7 @@
     private boolean mShutdown;
     private boolean mStarted;
     private boolean mBatchingStarted;
+    private AlarmManager.OnAlarmListener mBatchingAlarm;
     private long mStartedChangedElapsedRealtime;
     private int mFixInterval = 1000;
 
@@ -845,18 +856,15 @@
                 mFixInterval = Integer.MAX_VALUE;
             }
 
-            // requested batch size, or zero to disable batching
-            long batchSize =
-                    mBatchingEnabled ? mProviderRequest.getMaxUpdateDelayMillis() / Math.max(
-                            mFixInterval, 1) : 0;
-            if (batchSize < getBatchSize()) {
-                batchSize = 0;
-            }
+            int batchIntervalMs = max(mFixInterval, MIN_BATCH_INTERVAL_MS);
+            long batchLengthMs = Math.min(mProviderRequest.getMaxUpdateDelayMillis(),
+                    MAX_BATCH_LENGTH_MS);
 
             // apply request to GPS engine
-            if (batchSize > 0) {
+            if (mBatchingEnabled && batchLengthMs / 2 >= batchIntervalMs) {
                 stopNavigating();
-                startBatching();
+                mFixInterval = batchIntervalMs;
+                startBatching(batchLengthMs);
             } else {
                 stopBatching();
 
@@ -875,7 +883,7 @@
                     if (mFixInterval >= NO_FIX_TIMEOUT) {
                         // set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
                         // and our fix interval is not short
-                        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                        mAlarmManager.set(ELAPSED_REALTIME_WAKEUP,
                                 SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, TAG,
                                 mTimeoutListener, mHandler);
                     }
@@ -1066,7 +1074,7 @@
                 // set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
                 // and our fix interval is not short
                 if (mFixInterval >= NO_FIX_TIMEOUT) {
-                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    mAlarmManager.set(ELAPSED_REALTIME_WAKEUP,
                             SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, TAG, mTimeoutListener,
                             mHandler);
                 }
@@ -1090,12 +1098,37 @@
         mAlarmManager.cancel(mWakeupListener);
     }
 
-    private void startBatching() {
+    private void startBatching(long batchLengthMs) {
+        long batchSize = batchLengthMs / mFixInterval;
+
         if (DEBUG) {
-            Log.d(TAG, "startBatching " + mFixInterval);
+            Log.d(TAG, "startBatching " + mFixInterval + " " + batchLengthMs);
         }
         if (mGnssNative.startBatch(MILLISECONDS.toNanos(mFixInterval), true)) {
             mBatchingStarted = true;
+
+            if (batchSize < getBatchSize()) {
+                // if the batch size is smaller than the hardware batch size, use an alarm to flush
+                // locations as appropriate
+                mBatchingAlarm = () -> {
+                    boolean flush = false;
+                    synchronized (mLock) {
+                        if (mBatchingAlarm != null) {
+                            flush = true;
+                            mAlarmManager.setExact(ELAPSED_REALTIME_WAKEUP,
+                                    SystemClock.elapsedRealtime() + batchLengthMs, TAG,
+                                    mBatchingAlarm, FgThread.getHandler());
+                        }
+                    }
+
+                    if (flush) {
+                        mGnssNative.flushBatch();
+                    }
+                };
+                mAlarmManager.setExact(ELAPSED_REALTIME_WAKEUP,
+                        SystemClock.elapsedRealtime() + batchLengthMs, TAG,
+                        mBatchingAlarm, FgThread.getHandler());
+            }
         } else {
             Log.e(TAG, "native_start_batch failed in startBatching()");
         }
@@ -1104,6 +1137,10 @@
     private void stopBatching() {
         if (DEBUG) Log.d(TAG, "stopBatching");
         if (mBatchingStarted) {
+            if (mBatchingAlarm != null) {
+                mAlarmManager.cancel(mBatchingAlarm);
+                mBatchingAlarm = null;
+            }
             mGnssNative.stopBatch();
             mBatchingStarted = false;
         }
@@ -1120,7 +1157,7 @@
         // stop GPS until our next fix interval arrives
         stopNavigating();
         long now = SystemClock.elapsedRealtime();
-        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, now + mFixInterval, TAG,
+        mAlarmManager.set(ELAPSED_REALTIME_WAKEUP, now + mFixInterval, TAG,
                 mWakeupListener, mHandler);
     }
 
@@ -1485,16 +1522,50 @@
             Log.d(TAG, "Location batch of size " + locations.length + " reported");
         }
 
+        if (locations.length > 0) {
+            // attempt to fix up timestamps if necessary
+            if (locations.length > 1) {
+                // check any realtimes outside of expected bounds
+                boolean fixRealtime = false;
+                for (int i = locations.length - 2; i >= 0; i--) {
+                    long timeDeltaMs = locations[i + 1].getTime() - locations[i].getTime();
+                    long realtimeDeltaMs = locations[i + 1].getElapsedRealtimeMillis()
+                            - locations[i].getElapsedRealtimeMillis();
+                    if (abs(timeDeltaMs - realtimeDeltaMs) > MAX_BATCH_TIMESTAMP_DELTA_MS) {
+                        fixRealtime = true;
+                        break;
+                    }
+                }
+
+                if (fixRealtime) {
+                    // sort for monotonically increasing time before fixing realtime - realtime will
+                    // thus also be monotonically increasing
+                    Arrays.sort(locations,
+                            Comparator.comparingLong(Location::getTime));
+
+                    long expectedDeltaMs =
+                            locations[locations.length - 1].getTime()
+                                    - locations[locations.length - 1].getElapsedRealtimeMillis();
+                    for (int i = locations.length - 2; i >= 0; i--) {
+                        locations[i].setElapsedRealtimeNanos(
+                                MILLISECONDS.toNanos(
+                                        max(locations[i].getTime() - expectedDeltaMs, 0)));
+                    }
+                } else {
+                    // sort for monotonically increasing realttime
+                    Arrays.sort(locations,
+                            Comparator.comparingLong(Location::getElapsedRealtimeNanos));
+                }
+            }
+
+            reportLocation(LocationResult.wrap(locations).validate());
+        }
+
         Runnable[] listeners;
         synchronized (mLock) {
             listeners = mFlushListeners.toArray(new Runnable[0]);
             mFlushListeners.clear();
         }
-
-        if (locations.length > 0) {
-            reportLocation(LocationResult.wrap(locations).validate());
-        }
-
         for (Runnable listener : listeners) {
             listener.run();
         }
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 8955c28..62d8c32 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -1078,7 +1078,7 @@
         }
 
         private void onTransportFailure(Exception e) {
-            if (e instanceof RemoteException) {
+            if (e instanceof PendingIntent.CanceledException) {
                 Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
                 synchronized (mLock) {
                     remove();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b54e8f9..78c909d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4363,8 +4363,7 @@
                             final int userId = r.getSbn().getUserId();
                             if (userId != info.userid && userId != UserHandle.USER_ALL &&
                                     !mUserProfiles.isCurrentProfile(userId)) {
-                                throw new SecurityException("Disallowed call from listener: "
-                                        + info.service);
+                                continue;
                             }
                             cancelNotificationFromListenerLocked(info, callingUid, callingPid,
                                     r.getSbn().getPackageName(), r.getSbn().getTag(),
@@ -4431,8 +4430,7 @@
                         final int userId = r.getSbn().getUserId();
                         if (userId != info.userid && userId != UserHandle.USER_ALL
                                 && !mUserProfiles.isCurrentProfile(userId)) {
-                            throw new SecurityException("Disallowed call from listener: "
-                                    + info.service);
+                            continue;
                         }
                         seen.add(r);
                         if (!r.isSeen()) {
@@ -7451,15 +7449,21 @@
                         sentAccessibilityEvent = true;
                     }
                     if (DBG) Slog.v(TAG, "Interrupting!");
+                    boolean isInsistentUpdate = isInsistentUpdate(record);
                     if (hasValidSound) {
-                        if (isInCall()) {
-                            playInCallNotification();
+                        if (isInsistentUpdate) {
+                            // don't reset insistent sound, it's jarring
                             beep = true;
                         } else {
-                            beep = playSound(record, soundUri);
-                        }
-                        if(beep) {
-                            mSoundNotificationKey = key;
+                            if (isInCall()) {
+                                playInCallNotification();
+                                beep = true;
+                            } else {
+                                beep = playSound(record, soundUri);
+                            }
+                            if (beep) {
+                                mSoundNotificationKey = key;
+                            }
                         }
                     }
 
@@ -7467,9 +7471,13 @@
                             mAudioManager.getRingerModeInternal()
                                     == AudioManager.RINGER_MODE_SILENT;
                     if (!isInCall() && hasValidVibrate && !ringerModeSilent) {
-                        buzz = playVibration(record, vibration, hasValidSound);
-                        if(buzz) {
-                            mVibrateNotificationKey = key;
+                        if (isInsistentUpdate) {
+                            buzz = true;
+                        } else {
+                            buzz = playVibration(record, vibration, hasValidSound);
+                            if (buzz) {
+                                mVibrateNotificationKey = key;
+                            }
                         }
                     }
                 } else if ((record.getFlags() & Notification.FLAG_INSISTENT) != 0) {
@@ -7573,6 +7581,19 @@
     }
 
     @GuardedBy("mNotificationLock")
+    boolean isInsistentUpdate(final NotificationRecord record) {
+        return (Objects.equals(record.getKey(), mSoundNotificationKey)
+                || Objects.equals(record.getKey(), mVibrateNotificationKey))
+                && isCurrentlyInsistent();
+    }
+
+    @GuardedBy("mNotificationLock")
+    boolean isCurrentlyInsistent() {
+        return isLoopingRingtoneNotification(mNotificationsByKey.get(mSoundNotificationKey))
+                || isLoopingRingtoneNotification(mNotificationsByKey.get(mVibrateNotificationKey));
+    }
+
+    @GuardedBy("mNotificationLock")
     boolean shouldMuteNotificationLocked(final NotificationRecord record) {
         // Suppressed because it's a silent update
         final Notification notification = record.getNotification();
@@ -7611,10 +7632,8 @@
             return true;
         }
 
-        // A looping ringtone, such as an incoming call is playing
-        if (isLoopingRingtoneNotification(mNotificationsByKey.get(mSoundNotificationKey))
-                || isLoopingRingtoneNotification(
-                        mNotificationsByKey.get(mVibrateNotificationKey))) {
+        // A different looping ringtone, such as an incoming call is playing
+        if (isCurrentlyInsistent() && !isInsistentUpdate(record)) {
             return true;
         }
 
@@ -8758,10 +8777,22 @@
 
     void snoozeNotificationInt(String key, long duration, String snoozeCriterionId,
             ManagedServiceInfo listener) {
-        String listenerName = listener == null ? null : listener.component.toShortString();
+        if (listener == null) {
+            return;
+        }
+        String listenerName = listener.component.toShortString();
         if ((duration <= 0 && snoozeCriterionId == null) || key == null) {
             return;
         }
+        synchronized (mNotificationLock) {
+            final NotificationRecord r = findInCurrentAndSnoozedNotificationByKeyLocked(key);
+            if (r == null) {
+                return;
+            }
+            if (!listener.enabledAndUserMatches(r.getSbn().getNormalizedUserId())){
+                return;
+            }
+        }
 
         if (DBG) {
             Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, duration,
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index 7112ae1..628a322 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -22,6 +22,7 @@
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
 
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.Notification;
@@ -44,6 +45,8 @@
 import android.os.RemoteException;
 import android.os.ShellCommand;
 import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.util.Slog;
 
@@ -392,19 +395,28 @@
                                     + "--context <snooze-criterion-id>) <key>");
                             return 1;
                     }
-                    if (null == mDirectService.getNotificationRecord(key)) {
-                        pw.println("error: no notification matching key: " + key);
-                        return 1;
-                    }
                     if (duration > 0 || criterion != null) {
+                        ShellNls nls = new ShellNls();
+                        nls.registerAsSystemService(mDirectService.getContext(),
+                                new ComponentName(nls.getClass().getPackageName(),
+                                        nls.getClass().getName()),
+                                ActivityManager.getCurrentUser());
+                        if (!waitForBind(nls)) {
+                            pw.println("error: could not bind a listener in time");
+                            return 1;
+                        }
                         if (duration > 0) {
                             pw.println(String.format("snoozing <%s> until time: %s", key,
                                     new Date(System.currentTimeMillis() + duration)));
+                            nls.snoozeNotification(key, duration);
                         } else {
                             pw.println(String.format("snoozing <%s> until criterion: %s", key,
                                     criterion));
+                            nls.snoozeNotification(key, criterion);
                         }
-                        mDirectService.snoozeNotificationInt(key, duration, criterion, null);
+                        waitForSnooze(nls, key);
+                        nls.unregisterAsSystemService();
+                        waitForUnbind(nls);
                     } else {
                         pw.println("error: invalid value for --" + subflag + ": " + flagarg);
                         return 1;
@@ -527,14 +539,17 @@
                     final PendingIntent pi;
                     if ("broadcast".equals(intentKind)) {
                         pi = PendingIntent.getBroadcastAsUser(
-                                context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED,
+                                context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+                                        | PendingIntent.FLAG_MUTABLE_UNAUDITED,
                                 UserHandle.CURRENT);
                     } else if ("service".equals(intentKind)) {
                         pi = PendingIntent.getService(
-                                context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                                context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+                                        | PendingIntent.FLAG_MUTABLE_UNAUDITED);
                     } else {
                         pi = PendingIntent.getActivityAsUser(
-                                context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED, null,
+                                context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+                                        | PendingIntent.FLAG_MUTABLE_UNAUDITED, null,
                                 UserHandle.CURRENT);
                     }
                     builder.setContentIntent(pi);
@@ -685,9 +700,79 @@
         return 0;
     }
 
+    private void waitForSnooze(ShellNls nls, String key) {
+        for (int i = 0; i < 20; i++) {
+            StatusBarNotification[] sbns = nls.getSnoozedNotifications();
+            for (StatusBarNotification sbn : sbns) {
+                if (sbn.getKey().equals(key)) {
+                    return;
+                }
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        return;
+    }
+
+    private boolean waitForBind(ShellNls nls) {
+        for (int i = 0; i < 20; i++) {
+            if (nls.isConnected) {
+                Slog.i(TAG, "Bound Shell NLS");
+                return true;
+            } else {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return false;
+    }
+
+    private void waitForUnbind(ShellNls nls) {
+        for (int i = 0; i < 10; i++) {
+            if (!nls.isConnected) {
+                Slog.i(TAG, "Unbound Shell NLS");
+                return;
+            } else {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
     @Override
     public void onHelp() {
         getOutPrintWriter().println(USAGE);
     }
+
+    @SuppressLint("OverrideAbstract")
+    private static class ShellNls extends NotificationListenerService {
+        private static ShellNls
+                sNotificationListenerInstance = null;
+        boolean isConnected;
+
+        @Override
+        public void onListenerConnected() {
+            super.onListenerConnected();
+            sNotificationListenerInstance = this;
+            isConnected = true;
+        }
+        @Override
+        public void onListenerDisconnected() {
+            isConnected = false;
+        }
+
+        public static ShellNls getInstance() {
+            return sNotificationListenerInstance;
+        }
+    }
 }
 
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 96bde3d..e8a3a81 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -330,8 +330,7 @@
                                             }
                                         }
 
-                                        if (isShortcutOk(channel) && isDeletionOk(channel)
-                                                && !channel.isSoundMissing()) {
+                                        if (isShortcutOk(channel) && isDeletionOk(channel)) {
                                             r.channels.put(id, channel);
                                         }
                                     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f6acad0..1eb047b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2134,6 +2134,13 @@
         boolean filterAppAccess(String packageName, int callingUid, int userId);
         @LiveImplementation(override = LiveImplementation.MANDATORY)
         void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState);
+        @LiveImplementation(override = LiveImplementation.NOT_ALLOWED)
+        FindPreferredActivityBodyResult findPreferredActivityInternal(Intent intent,
+                String resolvedType, int flags, List<ResolveInfo> query, boolean always,
+                boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered);
+        @LiveImplementation(override = LiveImplementation.NOT_ALLOWED)
+        ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType, int flags,
+                List<ResolveInfo> query, boolean debug, int userId);
     }
 
     /**
@@ -2916,7 +2923,24 @@
             }
             allHomeCandidates.addAll(resolveInfos);
 
-            final String packageName = mDefaultAppProvider.getDefaultHome(userId);
+            String packageName = mDefaultAppProvider.getDefaultHome(userId);
+            if (packageName == null) {
+                // Role changes are not and cannot be atomic because its implementation lives inside
+                // a system app, so when the home role changes, there is a window when the previous
+                // role holder is removed and the new role holder is granted the preferred activity,
+                // but hasn't become the role holder yet. However, this case may be easily hit
+                // because the preferred activity change triggers a broadcast and receivers may try
+                // to get the default home activity there. So we need to fix it for this time
+                // window, and an easy workaround is to fallback to the current preferred activity.
+                final int appId = UserHandle.getAppId(Binder.getCallingUid());
+                final boolean filtered = appId >= Process.FIRST_APPLICATION_UID;
+                FindPreferredActivityBodyResult result = findPreferredActivityInternal(
+                        intent, null, 0, resolveInfos, true, false, false, userId, filtered);
+                ResolveInfo preferredResolveInfo =  result.mPreferredResolveInfo;
+                if (preferredResolveInfo != null && preferredResolveInfo.activityInfo != null) {
+                    packageName = preferredResolveInfo.activityInfo.packageName;
+                }
+            }
             if (packageName == null) {
                 return null;
             }
@@ -4848,6 +4872,284 @@
                 }
             } // switch
         }
+
+        // The body of findPreferredActivity.
+        protected FindPreferredActivityBodyResult findPreferredActivityBody(
+                Intent intent, String resolvedType, int flags,
+                List<ResolveInfo> query, boolean always,
+                boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered,
+                int callingUid, boolean isDeviceProvisioned) {
+            FindPreferredActivityBodyResult result = new FindPreferredActivityBodyResult();
+
+            flags = updateFlagsForResolve(
+                    flags, userId, callingUid, false /*includeInstantApps*/,
+                    isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId,
+                            resolvedType, flags));
+            intent = updateIntentForResolve(intent);
+
+            // Try to find a matching persistent preferred activity.
+            result.mPreferredResolveInfo = findPersistentPreferredActivityLP(intent,
+                    resolvedType, flags, query, debug, userId);
+
+            // If a persistent preferred activity matched, use it.
+            if (result.mPreferredResolveInfo != null) {
+                return result;
+            }
+
+            PreferredIntentResolver pir = mSettings.getPreferredActivities(userId);
+            // Get the list of preferred activities that handle the intent
+            if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities...");
+            List<PreferredActivity> prefs = pir != null
+                    ? pir.queryIntent(intent, resolvedType,
+                            (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
+                            userId)
+                    : null;
+            if (prefs != null && prefs.size() > 0) {
+
+                // First figure out how good the original match set is.
+                // We will only allow preferred activities that came
+                // from the same match quality.
+                int match = 0;
+
+                if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Figuring out best match...");
+
+                final int N = query.size();
+                for (int j = 0; j < N; j++) {
+                    final ResolveInfo ri = query.get(j);
+                    if (DEBUG_PREFERRED || debug) {
+                        Slog.v(TAG, "Match for " + ri.activityInfo
+                                + ": 0x" + Integer.toHexString(match));
+                    }
+                    if (ri.match > match) {
+                        match = ri.match;
+                    }
+                }
+
+                if (DEBUG_PREFERRED || debug) {
+                    Slog.v(TAG, "Best match: 0x" + Integer.toHexString(match));
+                }
+                match &= IntentFilter.MATCH_CATEGORY_MASK;
+                final int M = prefs.size();
+                for (int i = 0; i < M; i++) {
+                    final PreferredActivity pa = prefs.get(i);
+                    if (DEBUG_PREFERRED || debug) {
+                        Slog.v(TAG, "Checking PreferredActivity ds="
+                                + (pa.countDataSchemes() > 0 ? pa.getDataScheme(0) : "<none>")
+                                + "\n  component=" + pa.mPref.mComponent);
+                        pa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), "  ");
+                    }
+                    if (pa.mPref.mMatch != match) {
+                        if (DEBUG_PREFERRED || debug) {
+                            Slog.v(TAG, "Skipping bad match "
+                                    + Integer.toHexString(pa.mPref.mMatch));
+                        }
+                        continue;
+                    }
+                    // If it's not an "always" type preferred activity and that's what we're
+                    // looking for, skip it.
+                    if (always && !pa.mPref.mAlways) {
+                        if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping mAlways=false entry");
+                        continue;
+                    }
+                    final ActivityInfo ai = getActivityInfo(
+                            pa.mPref.mComponent, flags | MATCH_DISABLED_COMPONENTS
+                                    | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+                            userId);
+                    if (DEBUG_PREFERRED || debug) {
+                        Slog.v(TAG, "Found preferred activity:");
+                        if (ai != null) {
+                            ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), "  ");
+                        } else {
+                            Slog.v(TAG, "  null");
+                        }
+                    }
+                    final boolean excludeSetupWizardHomeActivity = isHomeIntent(intent)
+                            && !isDeviceProvisioned;
+                    final boolean allowSetMutation = !excludeSetupWizardHomeActivity
+                            && !queryMayBeFiltered;
+                    if (ai == null) {
+                        // Do not remove launcher's preferred activity during SetupWizard
+                        // due to it may not install yet
+                        if (!allowSetMutation) {
+                            continue;
+                        }
+
+                        // This previously registered preferred activity
+                        // component is no longer known.  Most likely an update
+                        // to the app was installed and in the new version this
+                        // component no longer exists.  Clean it up by removing
+                        // it from the preferred activities list, and skip it.
+                        Slog.w(TAG, "Removing dangling preferred activity: "
+                                + pa.mPref.mComponent);
+                        pir.removeFilter(pa);
+                        result.mChanged = true;
+                        continue;
+                    }
+                    for (int j = 0; j < N; j++) {
+                        final ResolveInfo ri = query.get(j);
+                        if (!ri.activityInfo.applicationInfo.packageName
+                                .equals(ai.applicationInfo.packageName)) {
+                            continue;
+                        }
+                        if (!ri.activityInfo.name.equals(ai.name)) {
+                            continue;
+                        }
+
+                        if (removeMatches && allowSetMutation) {
+                            pir.removeFilter(pa);
+                            result.mChanged = true;
+                            if (DEBUG_PREFERRED) {
+                                Slog.v(TAG, "Removing match " + pa.mPref.mComponent);
+                            }
+                            break;
+                        }
+
+                        // Okay we found a previously set preferred or last chosen app.
+                        // If the result set is different from when this
+                        // was created, and is not a subset of the preferred set, we need to
+                        // clear it and re-ask the user their preference, if we're looking for
+                        // an "always" type entry.
+
+                        if (always && !pa.mPref.sameSet(query, excludeSetupWizardHomeActivity)) {
+                            if (pa.mPref.isSuperset(query, excludeSetupWizardHomeActivity)) {
+                                if (allowSetMutation) {
+                                    // some components of the set are no longer present in
+                                    // the query, but the preferred activity can still be reused
+                                    if (DEBUG_PREFERRED) {
+                                        Slog.i(TAG, "Result set changed, but PreferredActivity"
+                                                + " is still valid as only non-preferred"
+                                                + " components were removed for " + intent
+                                                + " type " + resolvedType);
+                                    }
+                                    // remove obsolete components and re-add the up-to-date
+                                    // filter
+                                    PreferredActivity freshPa = new PreferredActivity(pa,
+                                            pa.mPref.mMatch,
+                                            pa.mPref.discardObsoleteComponents(query),
+                                            pa.mPref.mComponent,
+                                            pa.mPref.mAlways);
+                                    pir.removeFilter(pa);
+                                    pir.addFilter(freshPa);
+                                    result.mChanged = true;
+                                } else {
+                                    if (DEBUG_PREFERRED) {
+                                        Slog.i(TAG, "Do not remove preferred activity");
+                                    }
+                                }
+                            } else {
+                                if (allowSetMutation) {
+                                    Slog.i(TAG,
+                                            "Result set changed, dropping preferred activity "
+                                                    + "for " + intent + " type "
+                                                    + resolvedType);
+                                    if (DEBUG_PREFERRED) {
+                                        Slog.v(TAG,
+                                                "Removing preferred activity since set changed "
+                                                        + pa.mPref.mComponent);
+                                    }
+                                    pir.removeFilter(pa);
+                                    // Re-add the filter as a "last chosen" entry (!always)
+                                    PreferredActivity lastChosen = new PreferredActivity(
+                                            pa, pa.mPref.mMatch, null, pa.mPref.mComponent,
+                                            false);
+                                    pir.addFilter(lastChosen);
+                                    result.mChanged = true;
+                                }
+                                result.mPreferredResolveInfo = null;
+                                return result;
+                            }
+                        }
+
+                        // Yay! Either the set matched or we're looking for the last chosen
+                        if (DEBUG_PREFERRED || debug) {
+                            Slog.v(TAG, "Returning preferred activity: "
+                                    + ri.activityInfo.packageName + "/" + ri.activityInfo.name);
+                        }
+                        result.mPreferredResolveInfo = ri;
+                        return result;
+                    }
+                }
+            }
+            return result;
+        }
+
+        public final FindPreferredActivityBodyResult findPreferredActivityInternal(
+                Intent intent, String resolvedType, int flags,
+                List<ResolveInfo> query, boolean always,
+                boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
+
+            final int callingUid = Binder.getCallingUid();
+            // Do NOT hold the packages lock; this calls up into the settings provider which
+            // could cause a deadlock.
+            final boolean isDeviceProvisioned =
+                    android.provider.Settings.Global.getInt(mContext.getContentResolver(),
+                            android.provider.Settings.Global.DEVICE_PROVISIONED, 0) == 1;
+            // Find the preferred activity - the lock is held inside the method.
+            return findPreferredActivityBody(
+                    intent, resolvedType, flags, query, always, removeMatches, debug,
+                    userId, queryMayBeFiltered, callingUid, isDeviceProvisioned);
+        }
+
+        public final ResolveInfo findPersistentPreferredActivityLP(Intent intent,
+                String resolvedType,
+                int flags, List<ResolveInfo> query, boolean debug, int userId) {
+            final int N = query.size();
+            PersistentPreferredIntentResolver ppir =
+                    mSettings.getPersistentPreferredActivities(userId);
+            // Get the list of persistent preferred activities that handle the intent
+            if (DEBUG_PREFERRED || debug) {
+                Slog.v(TAG, "Looking for persistent preferred activities...");
+            }
+            List<PersistentPreferredActivity> pprefs = ppir != null
+                    ? ppir.queryIntent(intent, resolvedType,
+                            (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
+                            userId)
+                    : null;
+            if (pprefs != null && pprefs.size() > 0) {
+                final int M = pprefs.size();
+                for (int i = 0; i < M; i++) {
+                    final PersistentPreferredActivity ppa = pprefs.get(i);
+                    if (DEBUG_PREFERRED || debug) {
+                        Slog.v(TAG, "Checking PersistentPreferredActivity ds="
+                                + (ppa.countDataSchemes() > 0 ? ppa.getDataScheme(0) : "<none>")
+                                + "\n  component=" + ppa.mComponent);
+                        ppa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), "  ");
+                    }
+                    final ActivityInfo ai = getActivityInfo(ppa.mComponent,
+                            flags | MATCH_DISABLED_COMPONENTS, userId);
+                    if (DEBUG_PREFERRED || debug) {
+                        Slog.v(TAG, "Found persistent preferred activity:");
+                        if (ai != null) {
+                            ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), "  ");
+                        } else {
+                            Slog.v(TAG, "  null");
+                        }
+                    }
+                    if (ai == null) {
+                        // This previously registered persistent preferred activity
+                        // component is no longer known. Ignore it and do NOT remove it.
+                        continue;
+                    }
+                    for (int j = 0; j < N; j++) {
+                        final ResolveInfo ri = query.get(j);
+                        if (!ri.activityInfo.applicationInfo.packageName
+                                .equals(ai.applicationInfo.packageName)) {
+                            continue;
+                        }
+                        if (!ri.activityInfo.name.equals(ai.name)) {
+                            continue;
+                        }
+                        //  Found a persistent preference that can handle the intent.
+                        if (DEBUG_PREFERRED || debug) {
+                            Slog.v(TAG, "Returning persistent preferred activity: "
+                                    + ri.activityInfo.packageName + "/" + ri.activityInfo.name);
+                        }
+                        return ri;
+                    }
+                }
+            }
+            return null;
+        }
     }
 
     /**
@@ -5007,6 +5309,16 @@
                 super.dump(type, fd, pw, dumpState);
             }
         }
+        public final FindPreferredActivityBodyResult findPreferredActivityBody(Intent intent,
+                String resolvedType, int flags, List<ResolveInfo> query, boolean always,
+                boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered,
+                int callingUid, boolean isDeviceProvisioned) {
+            synchronized (mLock) {
+                return super.findPreferredActivityBody(intent, resolvedType, flags, query, always,
+                        removeMatches, debug, userId, queryMayBeFiltered, callingUid,
+                        isDeviceProvisioned);
+            }
+        }
     }
 
     /**
@@ -5574,6 +5886,28 @@
                 current.release();
             }
         }
+        public final FindPreferredActivityBodyResult findPreferredActivityInternal(Intent intent,
+                String resolvedType, int flags, List<ResolveInfo> query, boolean always,
+                boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
+            ThreadComputer current = live();
+            try {
+                return current.mComputer.findPreferredActivityInternal(intent, resolvedType, flags,
+                        query, always, removeMatches, debug, userId, queryMayBeFiltered);
+            } finally {
+                current.release();
+            }
+        }
+        public final ResolveInfo findPersistentPreferredActivityLP(Intent intent,
+                String resolvedType, int flags, List<ResolveInfo> query, boolean debug,
+                int userId) {
+            ThreadComputer current = live();
+            try {
+                return current.mComputer.findPersistentPreferredActivityLP(intent, resolvedType,
+                        flags, query, debug, userId);
+            } finally {
+                current.release();
+            }
+        }
     }
 
 
@@ -7222,6 +7556,7 @@
             t.traceEnd();
 
             mPermissionManager.readLegacyPermissionsTEMP(mSettings.mPermissions);
+            mPermissionManager.readLegacyPermissionStateTEMP();
 
             if (!mOnlyCore && mFirstBoot) {
                 requestCopyPreoptedFiles(mInjector);
@@ -7637,7 +7972,6 @@
                     + ((SystemClock.uptimeMillis()-startTime)/1000f)
                     + " seconds");
 
-            mPermissionManager.readLegacyPermissionStateTEMP();
             // If the build fingerprint has changed since the last time we booted,
             // we need to re-grant app permission to catch any new ones that
             // appear.  This is really a hack, and means that apps can in some
@@ -9070,7 +9404,7 @@
     /**
      * Update given intent when being used to request {@link ResolveInfo}.
      */
-    private Intent updateIntentForResolve(Intent intent) {
+    private static Intent updateIntentForResolve(Intent intent) {
         if (intent.getSelector() != null) {
             intent = intent.getSelector();
         }
@@ -10242,7 +10576,7 @@
                 userId);
         // Find any earlier preferred or last chosen entries and nuke them
         findPreferredActivityNotLocked(
-                intent, resolvedType, flags, query, 0, false, true, false, userId);
+                intent, resolvedType, flags, query, false, true, false, userId);
         // Add the new activity as the last chosen for this filter
         addPreferredActivity(filter, match, null, activity, false, userId,
                 "Setting last chosen", false);
@@ -10258,7 +10592,7 @@
         final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags,
                 userId);
         return findPreferredActivityNotLocked(
-                intent, resolvedType, flags, query, 0, false, false, false, userId);
+                intent, resolvedType, flags, query, false, false, false, userId);
     }
 
     private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
@@ -10300,7 +10634,7 @@
                 // If we have saved a preference for a preferred activity for
                 // this Intent, use that.
                 ResolveInfo ri = findPreferredActivityNotLocked(intent, resolvedType,
-                        flags, query, r0.priority, true, false, debug, userId, queryMayBeFiltered);
+                        flags, query, true, false, debug, userId, queryMayBeFiltered);
                 if (ri != null) {
                     return ri;
                 }
@@ -10413,287 +10747,72 @@
     }
 
     @GuardedBy("mLock")
-    private ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType,
+    private ResolveInfo findPersistentPreferredActivityLP(Intent intent,
+            String resolvedType,
             int flags, List<ResolveInfo> query, boolean debug, int userId) {
-        final int N = query.size();
-        PersistentPreferredIntentResolver ppir = mSettings.getPersistentPreferredActivities(userId);
-        // Get the list of persistent preferred activities that handle the intent
-        if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for presistent preferred activities...");
-        List<PersistentPreferredActivity> pprefs = ppir != null
-                ? ppir.queryIntent(intent, resolvedType,
-                        (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
-                        userId)
-                : null;
-        if (pprefs != null && pprefs.size() > 0) {
-            final int M = pprefs.size();
-            for (int i=0; i<M; i++) {
-                final PersistentPreferredActivity ppa = pprefs.get(i);
-                if (DEBUG_PREFERRED || debug) {
-                    Slog.v(TAG, "Checking PersistentPreferredActivity ds="
-                            + (ppa.countDataSchemes() > 0 ? ppa.getDataScheme(0) : "<none>")
-                            + "\n  component=" + ppa.mComponent);
-                    ppa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), "  ");
-                }
-                final ActivityInfo ai = getActivityInfo(ppa.mComponent,
-                        flags | MATCH_DISABLED_COMPONENTS, userId);
-                if (DEBUG_PREFERRED || debug) {
-                    Slog.v(TAG, "Found persistent preferred activity:");
-                    if (ai != null) {
-                        ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), "  ");
-                    } else {
-                        Slog.v(TAG, "  null");
-                    }
-                }
-                if (ai == null) {
-                    // This previously registered persistent preferred activity
-                    // component is no longer known. Ignore it and do NOT remove it.
-                    continue;
-                }
-                for (int j=0; j<N; j++) {
-                    final ResolveInfo ri = query.get(j);
-                    if (!ri.activityInfo.applicationInfo.packageName
-                            .equals(ai.applicationInfo.packageName)) {
-                        continue;
-                    }
-                    if (!ri.activityInfo.name.equals(ai.name)) {
-                        continue;
-                    }
-                    //  Found a persistent preference that can handle the intent.
-                    if (DEBUG_PREFERRED || debug) {
-                        Slog.v(TAG, "Returning persistent preferred activity: " +
-                                ri.activityInfo.packageName + "/" + ri.activityInfo.name);
-                    }
-                    return ri;
-                }
-            }
-        }
-        return null;
+        return mComputer.findPersistentPreferredActivityLP(intent,
+                resolvedType,
+                flags, query, debug, userId);
     }
 
-    private boolean isHomeIntent(Intent intent) {
+    private static boolean isHomeIntent(Intent intent) {
         return ACTION_MAIN.equals(intent.getAction())
                 && intent.hasCategory(CATEGORY_HOME)
                 && intent.hasCategory(CATEGORY_DEFAULT);
     }
 
+
+    // findPreferredActivityBody returns two items: a "things changed" flag and a
+    // ResolveInfo, which is the preferred activity itself.
+    private static class FindPreferredActivityBodyResult {
+        boolean mChanged;
+        ResolveInfo mPreferredResolveInfo;
+    }
+
+    private FindPreferredActivityBodyResult findPreferredActivityInternal(
+            Intent intent, String resolvedType, int flags,
+            List<ResolveInfo> query, boolean always,
+            boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
+        return mComputer.findPreferredActivityInternal(
+            intent, resolvedType, flags,
+            query, always,
+            removeMatches, debug, userId, queryMayBeFiltered);
+    }
+
     ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags,
-            List<ResolveInfo> query, int priority, boolean always,
+            List<ResolveInfo> query, boolean always,
             boolean removeMatches, boolean debug, int userId) {
         return findPreferredActivityNotLocked(
-                intent, resolvedType, flags, query, priority, always, removeMatches, debug, userId,
+                intent, resolvedType, flags, query, always, removeMatches, debug, userId,
                 UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID);
     }
 
     // TODO: handle preferred activities missing while user has amnesia
     /** <b>must not hold {@link #mLock}</b> */
     ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags,
-            List<ResolveInfo> query, int priority, boolean always,
+            List<ResolveInfo> query, boolean always,
             boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
         if (Thread.holdsLock(mLock)) {
             Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
                     + " is holding mLock", new Throwable());
         }
         if (!mUserManager.exists(userId)) return null;
-        final int callingUid = Binder.getCallingUid();
-        // Do NOT hold the packages lock; this calls up into the settings provider which
-        // could cause a deadlock.
-        final boolean isDeviceProvisioned =
-                android.provider.Settings.Global.getInt(mContext.getContentResolver(),
-                        android.provider.Settings.Global.DEVICE_PROVISIONED, 0) == 1;
-        flags = updateFlagsForResolve(
-                flags, userId, callingUid, false /*includeInstantApps*/,
-                isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
-                        flags));
-        intent = updateIntentForResolve(intent);
-        // writer
-        synchronized (mLock) {
-            // Try to find a matching persistent preferred activity.
-            ResolveInfo pri = findPersistentPreferredActivityLP(intent, resolvedType, flags, query,
-                    debug, userId);
 
-            // If a persistent preferred activity matched, use it.
-            if (pri != null) {
-                return pri;
+        FindPreferredActivityBodyResult body = findPreferredActivityInternal(
+                intent, resolvedType, flags, query, always,
+                removeMatches, debug, userId, queryMayBeFiltered);
+        if (body.mChanged) {
+            if (DEBUG_PREFERRED) {
+                Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
             }
-
-            PreferredIntentResolver pir = mSettings.getPreferredActivities(userId);
-            // Get the list of preferred activities that handle the intent
-            if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities...");
-            List<PreferredActivity> prefs = pir != null
-                    ? pir.queryIntent(intent, resolvedType,
-                            (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
-                            userId)
-                    : null;
-            if (prefs != null && prefs.size() > 0) {
-                boolean changed = false;
-                try {
-                    // First figure out how good the original match set is.
-                    // We will only allow preferred activities that came
-                    // from the same match quality.
-                    int match = 0;
-
-                    if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Figuring out best match...");
-
-                    final int N = query.size();
-                    for (int j=0; j<N; j++) {
-                        final ResolveInfo ri = query.get(j);
-                        if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Match for " + ri.activityInfo
-                                + ": 0x" + Integer.toHexString(match));
-                        if (ri.match > match) {
-                            match = ri.match;
-                        }
-                    }
-
-                    if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Best match: 0x"
-                            + Integer.toHexString(match));
-
-                    match &= IntentFilter.MATCH_CATEGORY_MASK;
-                    final int M = prefs.size();
-                    for (int i=0; i<M; i++) {
-                        final PreferredActivity pa = prefs.get(i);
-                        if (DEBUG_PREFERRED || debug) {
-                            Slog.v(TAG, "Checking PreferredActivity ds="
-                                    + (pa.countDataSchemes() > 0 ? pa.getDataScheme(0) : "<none>")
-                                    + "\n  component=" + pa.mPref.mComponent);
-                            pa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), "  ");
-                        }
-                        if (pa.mPref.mMatch != match) {
-                            if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping bad match "
-                                    + Integer.toHexString(pa.mPref.mMatch));
-                            continue;
-                        }
-                        // If it's not an "always" type preferred activity and that's what we're
-                        // looking for, skip it.
-                        if (always && !pa.mPref.mAlways) {
-                            if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping mAlways=false entry");
-                            continue;
-                        }
-                        final ActivityInfo ai = getActivityInfo(
-                                pa.mPref.mComponent, flags | MATCH_DISABLED_COMPONENTS
-                                        | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
-                                userId);
-                        if (DEBUG_PREFERRED || debug) {
-                            Slog.v(TAG, "Found preferred activity:");
-                            if (ai != null) {
-                                ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), "  ");
-                            } else {
-                                Slog.v(TAG, "  null");
-                            }
-                        }
-                        final boolean excludeSetupWizardHomeActivity = isHomeIntent(intent)
-                                && !isDeviceProvisioned;
-                        final boolean allowSetMutation = !excludeSetupWizardHomeActivity
-                                && !queryMayBeFiltered;
-                        if (ai == null) {
-                            // Do not remove launcher's preferred activity during SetupWizard
-                            // due to it may not install yet
-                            if (!allowSetMutation) {
-                                continue;
-                            }
-
-                            // This previously registered preferred activity
-                            // component is no longer known.  Most likely an update
-                            // to the app was installed and in the new version this
-                            // component no longer exists.  Clean it up by removing
-                            // it from the preferred activities list, and skip it.
-                            Slog.w(TAG, "Removing dangling preferred activity: "
-                                    + pa.mPref.mComponent);
-                            pir.removeFilter(pa);
-                            changed = true;
-                            continue;
-                        }
-                        for (int j=0; j<N; j++) {
-                            final ResolveInfo ri = query.get(j);
-                            if (!ri.activityInfo.applicationInfo.packageName
-                                    .equals(ai.applicationInfo.packageName)) {
-                                continue;
-                            }
-                            if (!ri.activityInfo.name.equals(ai.name)) {
-                                continue;
-                            }
-
-                            if (removeMatches && allowSetMutation) {
-                                pir.removeFilter(pa);
-                                changed = true;
-                                if (DEBUG_PREFERRED) {
-                                    Slog.v(TAG, "Removing match " + pa.mPref.mComponent);
-                                }
-                                break;
-                            }
-
-                            // Okay we found a previously set preferred or last chosen app.
-                            // If the result set is different from when this
-                            // was created, and is not a subset of the preferred set, we need to
-                            // clear it and re-ask the user their preference, if we're looking for
-                            // an "always" type entry.
-
-                            if (always && !pa.mPref.sameSet(query, excludeSetupWizardHomeActivity)) {
-                                if (pa.mPref.isSuperset(query, excludeSetupWizardHomeActivity)) {
-                                    if (allowSetMutation) {
-                                        // some components of the set are no longer present in
-                                        // the query, but the preferred activity can still be reused
-                                        if (DEBUG_PREFERRED) {
-                                            Slog.i(TAG, "Result set changed, but PreferredActivity"
-                                                    + " is still valid as only non-preferred"
-                                                    + " components were removed for " + intent
-                                                    + " type " + resolvedType);
-                                        }
-                                        // remove obsolete components and re-add the up-to-date
-                                        // filter
-                                        PreferredActivity freshPa = new PreferredActivity(pa,
-                                                pa.mPref.mMatch,
-                                                pa.mPref.discardObsoleteComponents(query),
-                                                pa.mPref.mComponent,
-                                                pa.mPref.mAlways);
-                                        pir.removeFilter(pa);
-                                        pir.addFilter(freshPa);
-                                        changed = true;
-                                    } else {
-                                        if (DEBUG_PREFERRED) {
-                                            Slog.i(TAG, "Do not remove preferred activity");
-                                        }
-                                    }
-                                } else {
-                                    if (allowSetMutation) {
-                                        Slog.i(TAG,
-                                                "Result set changed, dropping preferred activity "
-                                                        + "for " + intent + " type "
-                                                        + resolvedType);
-                                        if (DEBUG_PREFERRED) {
-                                            Slog.v(TAG,
-                                                    "Removing preferred activity since set changed "
-                                                            + pa.mPref.mComponent);
-                                        }
-                                        pir.removeFilter(pa);
-                                        // Re-add the filter as a "last chosen" entry (!always)
-                                        PreferredActivity lastChosen = new PreferredActivity(
-                                                pa, pa.mPref.mMatch, null, pa.mPref.mComponent,
-                                                false);
-                                        pir.addFilter(lastChosen);
-                                        changed = true;
-                                    }
-                                    return null;
-                                }
-                            }
-
-                            // Yay! Either the set matched or we're looking for the last chosen
-                            if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Returning preferred activity: "
-                                    + ri.activityInfo.packageName + "/" + ri.activityInfo.name);
-                            return ri;
-                        }
-                    }
-                } finally {
-                    if (changed) {
-                        if (DEBUG_PREFERRED) {
-                            Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
-                        }
-                        scheduleWritePackageRestrictionsLocked(userId);
-                    }
-                }
+            synchronized (mLock) {
+                scheduleWritePackageRestrictionsLocked(userId);
             }
         }
-        if (DEBUG_PREFERRED || debug) Slog.v(TAG, "No preferred activity to return");
-        return null;
+        if ((DEBUG_PREFERRED || debug) && body.mPreferredResolveInfo == null) {
+            Slog.v(TAG, "No preferred activity to return");
+        }
+        return body.mPreferredResolveInfo;
     }
 
     /*
@@ -23470,7 +23589,7 @@
         final List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null,
                 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
         final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked(
-                intent, null, 0, resolveInfos, 0, true, false, false, userId);
+                intent, null, 0, resolveInfos, true, false, false, userId);
         final String packageName = preferredResolveInfo != null
                 && preferredResolveInfo.activityInfo != null
                 ? preferredResolveInfo.activityInfo.packageName : null;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e408822..b6ca67d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -484,6 +484,7 @@
     int mLidNavigationAccessibility;
     int mShortPressOnPowerBehavior;
     int mLongPressOnPowerBehavior;
+    long mLongPressOnPowerAssistantTimeoutMs;
     int mVeryLongPressOnPowerBehavior;
     int mDoublePressOnPowerBehavior;
     int mTriplePressOnPowerBehavior;
@@ -732,6 +733,9 @@
                     Settings.Global.POWER_BUTTON_LONG_PRESS), false, this,
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS), false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.POWER_BUTTON_VERY_LONG_PRESS), false, this,
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(Settings.Global.getUriFor(
@@ -1732,6 +1736,8 @@
                 com.android.internal.R.integer.config_shortPressOnPowerBehavior);
         mLongPressOnPowerBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_longPressOnPowerBehavior);
+        mLongPressOnPowerAssistantTimeoutMs = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_longPressOnPowerDurationMs);
         mVeryLongPressOnPowerBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_veryLongPressOnPowerBehavior);
         mDoublePressOnPowerBehavior = mContext.getResources().getInteger(
@@ -1955,7 +1961,7 @@
      */
     private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
         PowerKeyRule(int gestures) {
-            super(KEYCODE_POWER, gestures);
+            super(mContext, KEYCODE_POWER, gestures);
         }
 
         @Override
@@ -1970,6 +1976,15 @@
         }
 
         @Override
+        long getLongPressTimeoutMs() {
+            if (getResolvedLongPressOnPowerBehavior() == LONG_PRESS_POWER_ASSISTANT) {
+                return mLongPressOnPowerAssistantTimeoutMs;
+            } else {
+                return super.getLongPressTimeoutMs();
+            }
+        }
+
+        @Override
         void onLongPress(long eventTime) {
             if (mSingleKeyGestureDetector.beganFromNonInteractive()
                     && !mSupportLongPressPowerWhenNonInteractive) {
@@ -1997,7 +2012,7 @@
      */
     private final class BackKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
         BackKeyRule(int gestures) {
-            super(KEYCODE_BACK, gestures);
+            super(mContext, KEYCODE_BACK, gestures);
         }
 
         @Override
@@ -2017,7 +2032,7 @@
     }
 
     private void initSingleKeyGestureRules() {
-        mSingleKeyGestureDetector = new SingleKeyGestureDetector(mContext);
+        mSingleKeyGestureDetector = new SingleKeyGestureDetector();
 
         int powerKeyGestures = 0;
         if (hasVeryLongPressOnPowerBehavior()) {
@@ -2115,6 +2130,11 @@
                     Settings.Global.POWER_BUTTON_LONG_PRESS,
                     mContext.getResources().getInteger(
                             com.android.internal.R.integer.config_longPressOnPowerBehavior));
+            mLongPressOnPowerAssistantTimeoutMs = Settings.Global.getLong(
+                    mContext.getContentResolver(),
+                    Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS,
+                    mContext.getResources().getInteger(
+                            com.android.internal.R.integer.config_longPressOnPowerDurationMs));
             mVeryLongPressOnPowerBehavior = Settings.Global.getInt(resolver,
                     Settings.Global.POWER_BUTTON_VERY_LONG_PRESS,
                     mContext.getResources().getInteger(
@@ -5329,6 +5349,9 @@
                 pw.print("mLongPressOnPowerBehavior=");
                 pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
         pw.print(prefix);
+        pw.print("mLongPressOnPowerAssistantTimeoutMs=");
+        pw.println(mLongPressOnPowerAssistantTimeoutMs);
+        pw.print(prefix);
                 pw.print("mVeryLongPressOnPowerBehavior=");
                 pw.println(veryLongPressOnPowerBehaviorToString(mVeryLongPressOnPowerBehavior));
         pw.print(prefix);
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 1ef2bf9..6fee69b 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -44,9 +44,6 @@
     private static final int MSG_KEY_VERY_LONG_PRESS = 1;
     private static final int MSG_KEY_DELAYED_PRESS = 2;
 
-    private final long mLongPressTimeout;
-    private final long mVeryLongPressTimeout;
-
     private volatile int mKeyPressCounter;
     private boolean mBeganFromNonInteractive = false;
 
@@ -86,12 +83,19 @@
      *  </pre>
      */
     abstract static class SingleKeyRule {
+
         private final int mKeyCode;
         private final int mSupportedGestures;
+        private final long mDefaultLongPressTimeout;
+        private final long mDefaultVeryLongPressTimeout;
 
-        SingleKeyRule(int keyCode, @KeyGestureFlag int supportedGestures) {
+        SingleKeyRule(Context context, int keyCode, @KeyGestureFlag int supportedGestures) {
             mKeyCode = keyCode;
             mSupportedGestures = supportedGestures;
+            mDefaultLongPressTimeout =
+                ViewConfiguration.get(context).getDeviceGlobalActionKeyTimeout();
+            mDefaultVeryLongPressTimeout = context.getResources().getInteger(
+                com.android.internal.R.integer.config_veryLongPressTimeout);
         }
 
         /**
@@ -134,10 +138,28 @@
          */
         void onMultiPress(long downTime, int count) {}
         /**
+         *  Returns the timeout in milliseconds for a long press.
+         *
+         *  If multipress is also supported, this should always be greater than the multipress
+         *  timeout. If very long press is supported, this should always be less than the very long
+         *  press timeout.
+         */
+        long getLongPressTimeoutMs() {
+            return mDefaultLongPressTimeout;
+        }
+        /**
          *  Callback when long press has been detected.
          */
         void onLongPress(long eventTime) {}
         /**
+         *  Returns the timeout in milliseconds for a very long press.
+         *
+         *  If long press is supported, this should always be longer than the long press timeout.
+         */
+        long getVeryLongPressTimeoutMs() {
+            return mDefaultVeryLongPressTimeout;
+        }
+        /**
          *  Callback when very long press has been detected.
          */
         void onVeryLongPress(long eventTime) {}
@@ -151,10 +173,7 @@
         }
     }
 
-    public SingleKeyGestureDetector(Context context) {
-        mLongPressTimeout = ViewConfiguration.get(context).getDeviceGlobalActionKeyTimeout();
-        mVeryLongPressTimeout = context.getResources().getInteger(
-                com.android.internal.R.integer.config_veryLongPressTimeout);
+    public SingleKeyGestureDetector() {
         mHandler = new KeyHandler();
     }
 
@@ -225,14 +244,14 @@
                 final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0,
                         eventTime);
                 msg.setAsynchronous(true);
-                mHandler.sendMessageDelayed(msg, mLongPressTimeout);
+                mHandler.sendMessageDelayed(msg, mActiveRule.getLongPressTimeoutMs());
             }
 
             if (mActiveRule.supportVeryLongPress()) {
                 final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0,
                         eventTime);
                 msg.setAsynchronous(true);
-                mHandler.sendMessageDelayed(msg, mVeryLongPressTimeout);
+                mHandler.sendMessageDelayed(msg, mActiveRule.getVeryLongPressTimeoutMs());
             }
         } else {
             mHandler.removeMessages(MSG_KEY_LONG_PRESS);
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 7555a7f..c91d8de 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -96,6 +96,7 @@
     private static final int MSG_BROADCAST_ENHANCED_PREDICTION = 4;
     private static final int MSG_PROFILE_TIMED_OUT = 5;
     private static final int MSG_WIRED_CHARGING_STARTED = 6;
+    private static final int MSG_SCREEN_POLICY = 7;
 
     private static final long[] CHARGING_VIBRATION_TIME = {
             40, 40, 40, 40, 40, 40, 40, 40, 40, // ramp-up sampling rate = 40ms
@@ -120,6 +121,7 @@
     private final SuspendBlocker mSuspendBlocker;
     private final WindowManagerPolicy mPolicy;
     private final FaceDownDetector mFaceDownDetector;
+    private final ScreenUndimDetector mScreenUndimDetector;
     private final ActivityManagerInternal mActivityManagerInternal;
     private final InputManagerInternal mInputManagerInternal;
     private final InputMethodManagerInternal mInputMethodManagerInternal;
@@ -167,13 +169,14 @@
 
     public Notifier(Looper looper, Context context, IBatteryStats batteryStats,
             SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
-            FaceDownDetector faceDownDetector) {
+            FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
         mContext = context;
         mBatteryStats = batteryStats;
         mAppOps = mContext.getSystemService(AppOpsManager.class);
         mSuspendBlocker = suspendBlocker;
         mPolicy = policy;
         mFaceDownDetector = faceDownDetector;
+        mScreenUndimDetector = screenUndimDetector;
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
@@ -620,6 +623,22 @@
     }
 
     /**
+     * Called when the screen policy changes.
+     */
+    public void onScreenPolicyUpdate(int newPolicy) {
+        if (DEBUG) {
+            Slog.d(TAG, "onScreenPolicyUpdate: newPolicy=" + newPolicy);
+        }
+
+        synchronized (mLock) {
+            Message msg = mHandler.obtainMessage(MSG_SCREEN_POLICY);
+            msg.arg1 = newPolicy;
+            msg.setAsynchronous(true);
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    /**
      * Dumps data for bugreports.
      *
      * @param pw The stream to print to.
@@ -659,6 +678,7 @@
         tm.notifyUserActivity();
         mPolicy.userActivity();
         mFaceDownDetector.userActivity(event);
+        mScreenUndimDetector.userActivity();
     }
 
     void postEnhancedDischargePredictionBroadcast(long delayMs) {
@@ -812,6 +832,10 @@
         mSuspendBlocker.release();
     }
 
+    private void screenPolicyChanging(int screenPolicy) {
+        mScreenUndimDetector.recordScreenPolicy(screenPolicy);
+    }
+
     private void lockProfile(@UserIdInt int userId) {
         mTrustManager.setDeviceLockedForUser(userId, true /*locked*/);
     }
@@ -852,6 +876,9 @@
                 case MSG_WIRED_CHARGING_STARTED:
                     showWiredChargingStarted(msg.arg1);
                     break;
+                case MSG_SCREEN_POLICY:
+                    screenPolicyChanging(msg.arg1);
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 73bce31..e7e0425 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -274,6 +274,7 @@
     private final BatterySavingStats mBatterySavingStats;
     private final AttentionDetector mAttentionDetector;
     private final FaceDownDetector mFaceDownDetector;
+    private final ScreenUndimDetector mScreenUndimDetector;
     private final BinderService mBinderService;
     private final LocalService mLocalService;
     private final NativeWrapper mNativeWrapper;
@@ -837,9 +838,10 @@
     static class Injector {
         Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
                 SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
-                FaceDownDetector faceDownDetector) {
+                FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
             return new Notifier(
-                    looper, context, batteryStats, suspendBlocker, policy, faceDownDetector);
+                    looper, context, batteryStats, suspendBlocker, policy, faceDownDetector,
+                    screenUndimDetector);
         }
 
         SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
@@ -960,6 +962,7 @@
                 mInjector.createAmbientDisplaySuppressionController(context);
         mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock);
         mFaceDownDetector = new FaceDownDetector(this::onFlip);
+        mScreenUndimDetector = new ScreenUndimDetector();
 
         mBatterySavingStats = new BatterySavingStats(mLock);
         mBatterySaverPolicy =
@@ -1146,7 +1149,7 @@
             mBatteryStats = BatteryStatsService.getService();
             mNotifier = mInjector.createNotifier(Looper.getMainLooper(), mContext, mBatteryStats,
                     mInjector.createSuspendBlocker(this, "PowerManagerService.Broadcasts"),
-                    mPolicy, mFaceDownDetector);
+                    mPolicy, mFaceDownDetector, mScreenUndimDetector);
 
             mWirelessChargerDetector = mInjector.createWirelessChargerDetector(sensorManager,
                     mInjector.createSuspendBlocker(
@@ -1181,6 +1184,7 @@
         mBatterySaverController.systemReady();
         mBatterySaverPolicy.systemReady();
         mFaceDownDetector.systemReady(mContext);
+        mScreenUndimDetector.systemReady(mContext);
 
         // Register for settings changes.
         resolver.registerContentObserver(Settings.Secure.getUriFor(
@@ -3190,6 +3194,7 @@
 
                 final boolean ready = mDisplayManagerInternal.requestPowerState(groupId,
                         displayPowerRequest, mRequestWaitForNegativeProximity);
+                mNotifier.onScreenPolicyUpdate(displayPowerRequest.policy);
 
                 if (DEBUG_SPEW) {
                     Slog.d(TAG, "updateDisplayPowerStateLocked: displayReady=" + ready
diff --git a/services/core/java/com/android/server/power/ScreenUndimDetector.java b/services/core/java/com/android/server/power/ScreenUndimDetector.java
new file mode 100644
index 0000000..951bc1f
--- /dev/null
+++ b/services/core/java/com/android/server/power/ScreenUndimDetector.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2020 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.power;
+
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Detects when user manually undims the screen (x times) and acquires a wakelock to keep the screen
+ * on temporarily (without changing the screen timeout setting).
+ */
+public class ScreenUndimDetector {
+    private static final String TAG = "ScreenUndimDetector";
+    private static final boolean DEBUG = false;
+
+    private static final String UNDIM_DETECTOR_WAKE_LOCK = "UndimDetectorWakeLock";
+
+    /** DeviceConfig flag: is keep screen on feature enabled. */
+    static final String KEY_KEEP_SCREEN_ON_ENABLED = "keep_screen_on_enabled";
+    private static final boolean DEFAULT_KEEP_SCREEN_ON_ENABLED = true;
+    private static final int OUTCOME_POWER_BUTTON =
+            FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME__POWER_BUTTON;
+    private static final int OUTCOME_TIMEOUT =
+            FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME__TIMEOUT;
+    private boolean mKeepScreenOnEnabled;
+
+    /** DeviceConfig flag: how long should we keep the screen on. */
+    @VisibleForTesting
+    static final String KEY_KEEP_SCREEN_ON_FOR_MILLIS = "keep_screen_on_for_millis";
+    @VisibleForTesting
+    static final long DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS = TimeUnit.MINUTES.toMillis(10);
+    private long mKeepScreenOnForMillis;
+
+    /** DeviceConfig flag: how many user undims required to trigger keeping the screen on. */
+    @VisibleForTesting
+    static final String KEY_UNDIMS_REQUIRED = "undims_required";
+    @VisibleForTesting
+    static final int DEFAULT_UNDIMS_REQUIRED = 2;
+    private int mUndimsRequired;
+
+    /**
+     * DeviceConfig flag: what is the maximum duration between undims to still consider them
+     * consecutive.
+     */
+    @VisibleForTesting
+    static final String KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS =
+            "max_duration_between_undims_millis";
+    @VisibleForTesting
+    static final long DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS = TimeUnit.MINUTES.toMillis(5);
+    private long mMaxDurationBetweenUndimsMillis;
+
+    @VisibleForTesting
+    PowerManager.WakeLock mWakeLock;
+
+    @VisibleForTesting
+    int mCurrentScreenPolicy;
+    @VisibleForTesting
+    int mUndimCounter = 0;
+    @VisibleForTesting
+    long mUndimCounterStartedMillis;
+    private long mUndimOccurredTime = -1;
+    private long mInteractionAfterUndimTime = -1;
+    private InternalClock mClock;
+
+    public ScreenUndimDetector() {
+        mClock = new InternalClock();
+    }
+
+    ScreenUndimDetector(InternalClock clock) {
+        mClock = clock;
+    }
+
+    static class InternalClock {
+        public long getCurrentTime() {
+            return SystemClock.elapsedRealtime();
+        }
+    }
+
+    /** Should be called in parent's systemReady() */
+    public void systemReady(Context context) {
+        readValuesFromDeviceConfig();
+        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                context.getMainExecutor(),
+                (properties) -> onDeviceConfigChange(properties.getKeyset()));
+
+        final PowerManager powerManager = context.getSystemService(PowerManager.class);
+        mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK
+                        | PowerManager.ON_AFTER_RELEASE,
+                UNDIM_DETECTOR_WAKE_LOCK);
+    }
+
+    /**
+     * Launches a message that figures out the screen transitions and detects user undims. Must be
+     * called by the parent that is trying to update the screen policy.
+     */
+    public void recordScreenPolicy(int newPolicy) {
+        if (newPolicy == mCurrentScreenPolicy) {
+            return;
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "Screen policy transition: " + mCurrentScreenPolicy + " -> " + newPolicy);
+        }
+
+        // update the current policy with the new one immediately so we don't accidentally get
+        // into a loop (which is possible if the switch below triggers a new policy).
+        final int currentPolicy = mCurrentScreenPolicy;
+        mCurrentScreenPolicy = newPolicy;
+
+        if (!mKeepScreenOnEnabled) {
+            return;
+        }
+
+        switch (currentPolicy) {
+            case POLICY_DIM:
+                if (newPolicy == POLICY_BRIGHT) {
+                    final long now = mClock.getCurrentTime();
+                    final long timeElapsedSinceFirstUndim = now - mUndimCounterStartedMillis;
+                    if (timeElapsedSinceFirstUndim >= mMaxDurationBetweenUndimsMillis) {
+                        reset();
+                    }
+                    if (mUndimCounter == 0) {
+                        mUndimCounterStartedMillis = now;
+                    }
+
+                    mUndimCounter++;
+
+                    if (DEBUG) {
+                        Slog.d(TAG, "User undim, counter=" + mUndimCounter
+                                + " (required=" + mUndimsRequired + ")"
+                                + ", timeElapsedSinceFirstUndim=" + timeElapsedSinceFirstUndim
+                                + " (max=" + mMaxDurationBetweenUndimsMillis + ")");
+                    }
+                    if (mUndimCounter >= mUndimsRequired) {
+                        reset();
+                        if (DEBUG) {
+                            Slog.d(TAG, "Acquiring a wake lock for " + mKeepScreenOnForMillis);
+                        }
+                        if (mWakeLock != null) {
+                            mUndimOccurredTime = mClock.getCurrentTime();
+                            mWakeLock.acquire(mKeepScreenOnForMillis);
+                        }
+                    }
+                } else {
+                    if (newPolicy == POLICY_OFF || newPolicy == POLICY_DOZE) {
+                        checkAndLogUndim(OUTCOME_TIMEOUT);
+                    }
+                    reset();
+                }
+                break;
+            case POLICY_BRIGHT:
+                if (newPolicy == POLICY_OFF || newPolicy == POLICY_DOZE) {
+                    checkAndLogUndim(OUTCOME_POWER_BUTTON);
+                }
+                if (newPolicy != POLICY_DIM) {
+                    reset();
+                }
+                break;
+        }
+    }
+
+    @VisibleForTesting
+    void reset() {
+        if (DEBUG) {
+            Slog.d(TAG, "Resetting the undim detector");
+        }
+        mUndimCounter = 0;
+        mUndimCounterStartedMillis = 0;
+        if (mWakeLock != null && mWakeLock.isHeld()) {
+            mWakeLock.release();
+        }
+    }
+
+    private boolean readKeepScreenOnNotificationEnabled() {
+        return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_KEEP_SCREEN_ON_ENABLED,
+                DEFAULT_KEEP_SCREEN_ON_ENABLED);
+    }
+
+    private long readKeepScreenOnForMillis() {
+        return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_KEEP_SCREEN_ON_FOR_MILLIS,
+                DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS);
+    }
+
+    private int readUndimsRequired() {
+        int undimsRequired = DeviceConfig.getInt(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_UNDIMS_REQUIRED,
+                DEFAULT_UNDIMS_REQUIRED);
+
+        if (undimsRequired < 1 || undimsRequired > 5) {
+            Slog.e(TAG, "Provided undimsRequired=" + undimsRequired
+                    + " is not allowed [1, 5]; using the default=" + DEFAULT_UNDIMS_REQUIRED);
+            return DEFAULT_UNDIMS_REQUIRED;
+        }
+
+        return undimsRequired;
+    }
+
+    private long readMaxDurationBetweenUndimsMillis() {
+        return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS,
+                DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS);
+    }
+
+    private void onDeviceConfigChange(@NonNull Set<String> keys) {
+        for (String key : keys) {
+            Slog.i(TAG, "onDeviceConfigChange; key=" + key);
+            switch (key) {
+                case KEY_KEEP_SCREEN_ON_ENABLED:
+                case KEY_KEEP_SCREEN_ON_FOR_MILLIS:
+                case KEY_UNDIMS_REQUIRED:
+                case KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS:
+                    readValuesFromDeviceConfig();
+                    return;
+                default:
+                    Slog.i(TAG, "Ignoring change on " + key);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void readValuesFromDeviceConfig() {
+        mKeepScreenOnEnabled = readKeepScreenOnNotificationEnabled();
+        mKeepScreenOnForMillis = readKeepScreenOnForMillis();
+        mUndimsRequired = readUndimsRequired();
+        mMaxDurationBetweenUndimsMillis = readMaxDurationBetweenUndimsMillis();
+
+        Slog.i(TAG, "readValuesFromDeviceConfig():"
+                + "\nmKeepScreenOnForMillis=" + mKeepScreenOnForMillis
+                + "\nmKeepScreenOnNotificationEnabled=" + mKeepScreenOnEnabled
+                + "\nmUndimsRequired=" + mUndimsRequired);
+
+    }
+
+    /**
+     * The user interacted with the screen after an undim, indicating the phone is in use.
+     * We use this event for logging.
+     */
+    public void userActivity() {
+        if (mUndimOccurredTime != 1 && mInteractionAfterUndimTime == -1) {
+            mInteractionAfterUndimTime = mClock.getCurrentTime();
+        }
+    }
+
+    /**
+     * Checks and logs if an undim occurred.
+     *
+     * A log will occur if an undim seems to have resulted in a timeout or a direct screen off such
+     * as from a power button. Some outcomes may not be correctly assigned to a
+     * TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME value.
+     */
+    private void checkAndLogUndim(int outcome) {
+        if (mUndimOccurredTime != -1) {
+            long now = mClock.getCurrentTime();
+            FrameworkStatsLog.write(FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED,
+                    outcome,
+                    /* time_to_outcome_millis=*/  now - mUndimOccurredTime,
+                    /* time_to_first_interaction_millis= */
+                    mInteractionAfterUndimTime != -1 ? now - mInteractionAfterUndimTime : -1
+            );
+            mUndimOccurredTime = -1;
+            mInteractionAfterUndimTime = -1;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 3a7e13b..abe81e1 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -783,13 +783,14 @@
     @Override
     public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
             int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
-            int userId, String opPackageName, long operationId,
+            int userId, long operationId, String opPackageName, long requestId,
             @BiometricMultiSensorMode int multiSensorConfig) {
         enforceBiometricDialog();
         if (mBar != null) {
             try {
                 mBar.showAuthenticationDialog(promptInfo, receiver, sensorIds, credentialAllowed,
-                        requireConfirmation, userId, opPackageName, operationId, multiSensorConfig);
+                        requireConfirmation, userId, operationId, opPackageName, requestId,
+                        multiSensorConfig);
             } catch (RemoteException ex) {
             }
         }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 7713320..a51ed09 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wallpaper;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.app.WallpaperManager.COMMAND_REAPPLY;
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
@@ -207,7 +208,7 @@
      * wallpaper set and is created for the first time. The CLOSE_WRITE is triggered
      * every time the wallpaper is changed.
      */
-    private class WallpaperObserver extends FileObserver {
+    class WallpaperObserver extends FileObserver {
 
         final int mUserId;
         final WallpaperData mWallpaper;
@@ -225,7 +226,7 @@
             mWallpaperLockFile = new File(mWallpaperDir, WALLPAPER_LOCK_ORIG);
         }
 
-        private WallpaperData dataForEvent(boolean sysChanged, boolean lockChanged) {
+        WallpaperData dataForEvent(boolean sysChanged, boolean lockChanged) {
             WallpaperData wallpaper = null;
             synchronized (mLock) {
                 if (lockChanged) {
@@ -308,9 +309,18 @@
                             }
                             wallpaper.imageWallpaperPending = false;
                             if (sysWallpaperChanged) {
+                                IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
+                                    @Override
+                                    public void sendResult(Bundle data) throws RemoteException {
+                                        if (DEBUG) {
+                                            Slog.d(TAG, "publish system wallpaper changed!");
+                                        }
+                                        notifyWallpaperChanged(wallpaper);
+                                    }
+                                };
                                 // If this was the system wallpaper, rebind...
                                 bindWallpaperComponentLocked(mImageWallpaper, true,
-                                        false, wallpaper, null);
+                                        false, wallpaper, callback);
                                 notifyColorsWhich |= FLAG_SYSTEM;
                             }
                             if (lockWallpaperChanged
@@ -330,15 +340,9 @@
                             }
 
                             saveSettingsLocked(wallpaper.userId);
-
-                            // Publish completion *after* we've persisted the changes
-                            if (wallpaper.setComplete != null) {
-                                try {
-                                    wallpaper.setComplete.onWallpaperChanged();
-                                } catch (RemoteException e) {
-                                    // if this fails we don't really care; the setting app may just
-                                    // have crashed and that sort of thing is a fact of life.
-                                }
+                            // Notify the client immediately if only lockscreen wallpaper changed.
+                            if (lockWallpaperChanged && !sysWallpaperChanged) {
+                                notifyWallpaperChanged(wallpaper);
                             }
                         }
                     }
@@ -352,6 +356,18 @@
         }
     }
 
+    private void notifyWallpaperChanged(WallpaperData wallpaper) {
+        // Publish completion *after* we've persisted the changes
+        if (wallpaper.setComplete != null) {
+            try {
+                wallpaper.setComplete.onWallpaperChanged();
+            } catch (RemoteException e) {
+                // if this fails we don't really care; the setting app may just
+                // have crashed and that sort of thing is a fact of life.
+            }
+        }
+    }
+
     private void notifyLockWallpaperChanged() {
         final IWallpaperManagerCallback cb = mKeyguardListener;
         if (cb != null) {
@@ -363,7 +379,7 @@
         }
     }
 
-    private void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
+    void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
         if (wallpaper.connection != null) {
             wallpaper.connection.forEachDisplayConnector(connector -> {
                 notifyWallpaperColorsChangedOnDisplay(wallpaper, which, connector.mDisplayId);
@@ -567,7 +583,7 @@
      * Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
      * for display.
      */
-    private void generateCrop(WallpaperData wallpaper) {
+    void generateCrop(WallpaperData wallpaper) {
         boolean success = false;
 
         // Only generate crop for default display.
@@ -2045,7 +2061,21 @@
         }
     }
 
+    private boolean hasCrossUserPermission() {
+        final int interactPermission =
+                mContext.checkCallingPermission(INTERACT_ACROSS_USERS_FULL);
+        return interactPermission == PackageManager.PERMISSION_GRANTED;
+    }
+
+    @Override
     public boolean hasNamedWallpaper(String name) {
+        final int callingUser = UserHandle.getCallingUserId();
+        final boolean allowCrossUser = hasCrossUserPermission();
+        if (DEBUG) {
+            Slog.d(TAG, "hasNamedWallpaper() caller " + Binder.getCallingUid()
+                    + " cross-user?: " + allowCrossUser);
+        }
+
         synchronized (mLock) {
             List<UserInfo> users;
             final long ident = Binder.clearCallingIdentity();
@@ -2055,6 +2085,11 @@
                 Binder.restoreCallingIdentity(ident);
             }
             for (UserInfo user: users) {
+                if (!allowCrossUser && callingUser != user.id) {
+                    // No cross-user information for callers without permission
+                    continue;
+                }
+
                 // ignore managed profiles
                 if (user.isManagedProfile()) {
                     continue;
@@ -2814,7 +2849,7 @@
         return false;
     }
 
-    private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
+    boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
             boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
         if (DEBUG_LIVE) {
             Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
@@ -3101,7 +3136,7 @@
         return new JournaledFile(new File(base), new File(base + ".tmp"));
     }
 
-    private void saveSettingsLocked(int userId) {
+    void saveSettingsLocked(int userId) {
         JournaledFile journal = makeJournaledFile(userId);
         FileOutputStream fstream = null;
         try {
@@ -3250,7 +3285,7 @@
      * Important: this method loads settings to initialize the given user's wallpaper data if
      * there is no current in-memory state.
      */
-    private WallpaperData getWallpaperSafeLocked(int userId, int which) {
+    WallpaperData getWallpaperSafeLocked(int userId, int which) {
         // We're setting either just system (work with the system wallpaper),
         // both (also work with the system wallpaper), or just the lock
         // wallpaper (update against the existing lock wallpaper if any).
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 3a4faf7..e02e867 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -21,6 +21,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.os.Process.INVALID_UID;
+import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -53,6 +55,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
@@ -64,6 +67,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.service.voice.VoiceInteractionManagerInternal;
 import android.util.Slog;
 import android.view.RemoteAnimationDefinition;
@@ -74,6 +78,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.LocalServices;
 import com.android.server.Watchdog;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.vr.VrManagerInternal;
 
@@ -557,20 +562,45 @@
 
     @Override
     public int getLaunchedFromUid(IBinder token) {
+        if (!canGetLaunchedFrom()) {
+            return INVALID_UID;
+        }
         synchronized (mGlobalLock) {
             final ActivityRecord r = ActivityRecord.forTokenLocked(token);
-            return r != null ? r.launchedFromUid : android.os.Process.INVALID_UID;
+            return r != null ? r.launchedFromUid : INVALID_UID;
         }
     }
 
     @Override
     public String getLaunchedFromPackage(IBinder token) {
+        if (!canGetLaunchedFrom()) {
+            return null;
+        }
         synchronized (mGlobalLock) {
             final ActivityRecord r = ActivityRecord.forTokenLocked(token);
             return r != null ? r.launchedFromPackage : null;
         }
     }
 
+    /** Whether the caller can get the package or uid that launched its activity. */
+    private boolean canGetLaunchedFrom() {
+        final int uid = Binder.getCallingUid();
+        if (UserHandle.getAppId(uid) == SYSTEM_UID) {
+            return true;
+        }
+        final PackageManagerInternal pm = mService.mWindowManager.mPmInternal;
+        final AndroidPackage callingPkg = pm.getPackage(uid);
+        if (callingPkg == null) {
+            return false;
+        }
+        if (callingPkg.isSignedWithPlatformKey()) {
+            return true;
+        }
+        final String[] installerNames = pm.getKnownPackageNames(
+                PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.getUserId(uid));
+        return installerNames.length > 0 && callingPkg.getPackageName().equals(installerNames[0]);
+    }
+
     @Override
     public void setRequestedOrientation(IBinder token, int requestedOrientation) {
         final long origId = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 308df2f..0afc76d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -656,6 +656,12 @@
     boolean mLastImeShown;
 
     /**
+     * When set to true, the IME insets will be frozen until the next app becomes IME input target.
+     * @see InsetsPolicy#adjustVisibilityForIme
+     */
+    boolean mImeInsetsFrozenUntilStartInput;
+
+    /**
      * A flag to determine if this AR is in the process of closing or entering PIP. This is needed
      * to help AR know that the app is in the process of closing but hasn't yet started closing on
      * the WM side.
@@ -1363,6 +1369,7 @@
         }
         if (newTask != null && isState(RESUMED)) {
             newTask.setResumedActivity(this, "onParentChanged");
+            mImeInsetsFrozenUntilStartInput = false;
         }
 
         if (rootTask != null && rootTask.topRunningActivity() == this) {
@@ -2246,6 +2253,17 @@
         }
     }
 
+    void removeStartingWindowIfNeeded() {
+        // Removing the task snapshot after the task is actually focused (see
+        // Task#onWindowFocusChanged). Since some of the app contents may draw in this time and
+        // requires more times to draw finish, in case flicking may happen when removing the task
+        // snapshot too early. (i.e. Showing IME.)
+        if ((mStartingData instanceof SnapshotStartingData) && !getTask().isFocused()) {
+            return;
+        }
+        removeStartingWindow();
+    }
+
     void removeStartingWindow() {
         removeStartingWindowAnimation(true /* prepareAnimation */);
     }
@@ -4141,7 +4159,7 @@
         // The activity now gets access to the data associated with this Intent.
         mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(intentGrants,
                 getUriPermissionsLocked());
-        final ReferrerIntent rintent = new ReferrerIntent(intent, referrer);
+        final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer));
         boolean unsent = true;
         final boolean isTopActivityWhileSleeping = isTopRunningActivity() && isSleeping();
 
@@ -4759,6 +4777,7 @@
                     && imeInputTarget.getWindow().mActivityRecord == this
                     && mDisplayContent.mInputMethodWindow != null
                     && mDisplayContent.mInputMethodWindow.isVisible();
+            mImeInsetsFrozenUntilStartInput = true;
         }
 
         final DisplayContent displayContent = getDisplayContent();
@@ -5816,7 +5835,7 @@
             // own stuff.
             win.cancelAnimation();
         }
-        removeStartingWindow();
+        removeStartingWindowIfNeeded();
         updateReportedVisibilityLocked();
     }
 
@@ -5877,6 +5896,14 @@
             // closing activity having to wait until idle timeout to be stopped or destroyed if the
             // next activity won't report idle (e.g. repeated view animation).
             mTaskSupervisor.scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
+
+            // If the activity is visible, but no windows are eligible to start input, unfreeze
+            // to avoid permanently frozen IME insets.
+            if (mImeInsetsFrozenUntilStartInput && getWindow(
+                    win -> WindowManager.LayoutParams.mayUseInputMethod(win.mAttrs.flags))
+                    == null) {
+                mImeInsetsFrozenUntilStartInput = false;
+            }
         }
     }
 
@@ -7792,6 +7819,13 @@
         }
     }
 
+    @Override
+    void onResize() {
+        // Reset freezing IME insets flag when the activity resized.
+        mImeInsetsFrozenUntilStartInput = false;
+        super.onResize();
+    }
+
     /** Returns true if the configuration is compatible with this activity. */
     boolean isConfigurationCompatible(Configuration config) {
         final int orientation = getRequestedOrientation();
@@ -8482,6 +8516,19 @@
     }
 
     /**
+     * Gets the referrer package name with respect to package visibility. This method returns null
+     * if the given package is not visible to this activity.
+     */
+    String getFilteredReferrer(String referrerPackage) {
+        if (referrerPackage == null || (!referrerPackage.equals(packageName)
+                && mWmService.mPmInternal.filterAppAccess(
+                        referrerPackage, info.applicationInfo.uid, mUserId))) {
+            return null;
+        }
+        return referrerPackage;
+    }
+
+    /**
      * Determines whether this ActivityRecord can turn the screen on. It checks whether the flag
      * {@link ActivityRecord#getTurnScreenOnFlag} is set and checks whether the ActivityRecord
      * should be visible depending on Keyguard state.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e3459a1..efa67e9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -848,9 +848,9 @@
                         // and override configs.
                         mergedConfiguration.getGlobalConfiguration(),
                         mergedConfiguration.getOverrideConfiguration(), r.compat,
-                        r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(),
-                        r.getSavedState(), r.getPersistentSavedState(), results, newIntents,
-                        r.takeOptions(), isTransitionForward,
+                        r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
+                        proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
+                        results, newIntents, r.takeOptions(), isTransitionForward,
                         proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
                         r.createFixedRotationAdjustmentsIfNeeded(), r.shareableActivityToken,
                         r.getLaunchedFromBubble()));
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6892dbc..9335846 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3971,6 +3971,9 @@
     void updateImeInputAndControlTarget(WindowState target) {
         if (mImeInputTarget != target) {
             ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target);
+            if (target != null && target.mActivityRecord != null) {
+                target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false;
+            }
             setImeInputTarget(target);
             updateImeControlTarget();
         }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 73d31bf..94465ac 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -355,6 +355,8 @@
 
     private PointerLocationView mPointerLocationView;
 
+    private int mDisplayCutoutTouchableRegionSize;
+
     /**
      * The area covered by system windows which belong to another display. Forwarded insets is set
      * in case this is a virtual display, this is displayed on another display that has insets, and
@@ -1081,8 +1083,21 @@
                         (displayFrames, windowState, rect) -> {
                             rect.bottom = rect.top + getStatusBarHeight(displayFrames);
                         };
+                final TriConsumer<DisplayFrames, WindowState, Rect> gestureFrameProvider =
+                        (displayFrames, windowState, rect) -> {
+                            rect.bottom = rect.top + getStatusBarHeight(displayFrames);
+                            final DisplayCutout cutout =
+                                    displayFrames.mInsetsState.getDisplayCutout();
+                            if (cutout != null) {
+                                final Rect top = cutout.getBoundingRectTop();
+                                if (!top.isEmpty()) {
+                                    rect.bottom = rect.bottom + mDisplayCutoutTouchableRegionSize;
+                                }
+                            }
+                        };
                 mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, win, frameProvider);
-                mDisplayContent.setInsetProvider(ITYPE_TOP_MANDATORY_GESTURES, win, frameProvider);
+                mDisplayContent.setInsetProvider(
+                        ITYPE_TOP_MANDATORY_GESTURES, win, gestureFrameProvider);
                 mDisplayContent.setInsetProvider(ITYPE_TOP_TAPPABLE_ELEMENT, win, frameProvider);
                 break;
             case TYPE_NAVIGATION_BAR:
@@ -1993,11 +2008,14 @@
             mStatusBarHeightForRotation[landscapeRotation] =
                     mStatusBarHeightForRotation[seascapeRotation] =
                             res.getDimensionPixelSize(R.dimen.status_bar_height_landscape);
+            mDisplayCutoutTouchableRegionSize = res.getDimensionPixelSize(
+                    R.dimen.display_cutout_touchable_region_size);
         } else {
             mStatusBarHeightForRotation[portraitRotation] =
                     mStatusBarHeightForRotation[upsideDownRotation] =
                             mStatusBarHeightForRotation[landscapeRotation] =
                                     mStatusBarHeightForRotation[seascapeRotation] = 0;
+            mDisplayCutoutTouchableRegionSize = 0;
         }
 
         // Height of the navigation bar when presented horizontally at bottom
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 18ea738b..aa257f8 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -464,7 +464,8 @@
         if (mDragInProgress && isValidDropTarget(newWin, containsAppExtras, interceptsGlobalDrag)) {
             // Only allow the extras to be dispatched to a global-intercepting drag target
             ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null;
-            DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED, touchX, touchY,
+            DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED,
+                    newWin.translateToWindowX(touchX), newWin.translateToWindowY(touchY),
                     data, false /* includeDragSurface */,
                     null /* dragAndDropPermission */);
             try {
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index da47328..ed1e784 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -90,6 +90,24 @@
         onSourceChanged();
     }
 
+    @Override
+    protected boolean updateClientVisibility(InsetsControlTarget caller) {
+        boolean changed = super.updateClientVisibility(caller);
+        if (changed && caller.getRequestedVisibility(mSource.getType())) {
+            reportImeDrawnForOrganizer(caller);
+        }
+        return changed;
+    }
+
+    private void reportImeDrawnForOrganizer(InsetsControlTarget caller) {
+        if (caller.getWindow() != null && caller.getWindow().getTask() != null) {
+            if (caller.getWindow().getTask().isOrganized()) {
+                mWin.mWmService.mAtmService.mTaskOrganizerController.reportImeDrawnOnTask(
+                        caller.getWindow().getTask());
+            }
+        }
+    }
+
     private void onSourceChanged() {
         if (mLastSource.equals(mSource)) {
             return;
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 8c781a1..d417d56 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -39,6 +39,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
@@ -650,6 +651,7 @@
                 || type == TYPE_DOCK_DIVIDER
                 || type == TYPE_ACCESSIBILITY_OVERLAY
                 || type == TYPE_INPUT_CONSUMER
-                || type == TYPE_VOICE_INTERACTION;
+                || type == TYPE_VOICE_INTERACTION
+                || type == TYPE_STATUS_BAR_ADDITIONAL;
     }
 }
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index f2f1926..a8e1c1c 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -211,7 +211,7 @@
     InsetsState getInsetsForWindow(WindowState target) {
         final InsetsState originalState = mStateController.getInsetsForWindow(target);
         final InsetsState state = adjustVisibilityForTransientTypes(originalState);
-        return target.mIsImWindow ? adjustVisibilityForIme(state, state == originalState) : state;
+        return adjustVisibilityForIme(target, state, state == originalState);
     }
 
     /**
@@ -241,16 +241,37 @@
         return state;
     }
 
-    // Navigation bar insets is always visible to IME.
-    private static InsetsState adjustVisibilityForIme(InsetsState originalState,
+    private InsetsState adjustVisibilityForIme(WindowState w, InsetsState originalState,
             boolean copyState) {
-        final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR);
-        if (originalNavSource != null && !originalNavSource.isVisible()) {
-            final InsetsState state = copyState ? new InsetsState(originalState) : originalState;
-            final InsetsSource navSource = new InsetsSource(originalNavSource);
-            navSource.setVisible(true);
-            state.addSource(navSource);
-            return state;
+        if (w.mIsImWindow) {
+            // Navigation bar insets is always visible to IME.
+            final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR);
+            if (originalNavSource != null && !originalNavSource.isVisible()) {
+                final InsetsState state = copyState ? new InsetsState(originalState)
+                        : originalState;
+                final InsetsSource navSource = new InsetsSource(originalNavSource);
+                navSource.setVisible(true);
+                state.addSource(navSource);
+                return state;
+            }
+        } else if (w.mActivityRecord != null && w.mActivityRecord.mImeInsetsFrozenUntilStartInput) {
+            // During switching tasks with gestural navigation, if the IME is attached to
+            // one app window on that time, even the next app window is behind the IME window,
+            // conceptually the window should not receive the IME insets if the next window is
+            // not eligible IME requester and ready to show IME on top of it.
+            final boolean shouldImeAttachedToApp = mDisplayContent.shouldImeAttachedToApp();
+            final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME);
+
+            if (shouldImeAttachedToApp && originalImeSource != null) {
+                final boolean imeVisibility =
+                        w.mActivityRecord.mLastImeShown || w.getRequestedVisibility(ITYPE_IME);
+                final InsetsState state = copyState ? new InsetsState(originalState)
+                        : originalState;
+                final InsetsSource imeSource = new InsetsSource(originalImeSource);
+                imeSource.setVisible(imeVisibility);
+                state.addSource(imeSource);
+                return state;
+            }
         }
         return originalState;
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ced5af1..364594e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5144,7 +5144,7 @@
     /**
      * @return true if the task is currently focused.
      */
-    private boolean isFocused() {
+    boolean isFocused() {
         if (mDisplayContent == null || mDisplayContent.mCurrentFocus == null) {
             return false;
         }
@@ -5206,6 +5206,10 @@
      * @param hasFocus
      */
     void onWindowFocusChanged(boolean hasFocus) {
+        final ActivityRecord topAct = getTopVisibleActivity();
+        if (topAct != null && (topAct.mStartingData instanceof SnapshotStartingData)) {
+            topAct.removeStartingWindowIfNeeded();
+        }
         updateShadowsRadius(hasFocus, getSyncTransaction());
         // TODO(b/180525887): Un-comment once there is resolution on the bug.
         // dispatchTaskInfoChangedIfNeeded(false /* force */);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 09c5581..88467ba 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -733,6 +733,17 @@
         mPendingTaskEvents.clear();
     }
 
+    void reportImeDrawnOnTask(Task task) {
+        final TaskOrganizerState state = mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder());
+        if (state != null) {
+            try {
+                state.mOrganizer.mTaskOrganizer.onImeDrawnOnTask(task.mTaskId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Exception sending onImeDrawnOnTask callback", e);
+            }
+        }
+    }
+
     void onTaskInfoChanged(Task task, boolean force) {
         if (!task.mTaskAppearedSent) {
             // Skip if task still not appeared.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9caef70..e3b25a5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2717,8 +2717,8 @@
     }
 
     @Override
-    public boolean attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
-            Bundle options) {
+    public Configuration attachWindowContextToDisplayArea(IBinder clientToken, int
+            type, int displayId, Bundle options) {
         final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS,
                 "attachWindowContextToDisplayArea", false /* printLog */);
         final int callingUid = Binder.getCallingUid();
@@ -2729,15 +2729,17 @@
                 if (dc == null) {
                     ProtoLog.w(WM_ERROR, "attachWindowContextToDisplayArea: trying to attach"
                             + " to a non-existing display:%d", displayId);
-                    return false;
+                    return null;
                 }
                 // TODO(b/155340867): Investigate if we still need roundedCornerOverlay after
                 // the feature b/155340867 is completed.
                 final DisplayArea da = dc.findAreaForWindowType(type, options,
                         callerCanManageAppTokens, false /* roundedCornerOverlay */);
+                // TODO(b/190019118): Avoid to send onConfigurationChanged because it has been done
+                //  in return value of attachWindowContextToDisplayArea.
                 mWindowContextListenerController.registerWindowContainerListener(clientToken, da,
                         callingUid, type, options);
-                return true;
+                return da.getConfiguration();
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -7070,6 +7072,7 @@
                             "requestScrollCapture: caught exception dispatching to window."
                                     + "token=%s", targetWindow.mClient.asBinder());
                     responseBuilder.setWindowTitle(targetWindow.getName());
+                    responseBuilder.setPackageName(targetWindow.getOwningPackage());
                     responseBuilder.setDescription(String.format("caught exception: %s", e));
                     listener.onScrollCaptureResponse(responseBuilder.build());
                 }
@@ -8597,8 +8600,9 @@
             if (imeTargetWindowTask == null) {
                 return false;
             }
-            final TaskSnapshot snapshot = mAtmService.getTaskSnapshot(imeTargetWindowTask.mTaskId,
-                    false /* isLowResolution */);
+            final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId,
+                    imeTargetWindowTask.mUserId, false /* isLowResolution */,
+                    false /* restoreFromDisk */);
             return snapshot != null && snapshot.hasImeSurface();
         }
     }
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index e319e3f..4190a91 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -55,6 +55,7 @@
 
 #define SYNC_RECEIVED_WHILE_FROZEN (1)
 #define ASYNC_RECEIVED_WHILE_FROZEN (2)
+#define TXNS_PENDING_WHILE_FROZEN (4)
 
 namespace android {
 
@@ -232,17 +233,20 @@
     compactProcessOrFallback(pid, compactionFlags);
 }
 
-static void com_android_server_am_CachedAppOptimizer_freezeBinder(
+static jint com_android_server_am_CachedAppOptimizer_freezeBinder(
         JNIEnv *env, jobject clazz, jint pid, jboolean freeze) {
 
-    if (IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */) != 0) {
+    jint retVal = IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */);
+    if (retVal != 0 && retVal != -EAGAIN) {
         jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
     }
+
+    return retVal;
 }
 
 static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv *env,
         jobject clazz, jint pid) {
-    bool syncReceived = false, asyncReceived = false;
+    uint32_t syncReceived = 0, asyncReceived = 0;
 
     int error = IPCThreadState::getProcessFreezeInfo(pid, &syncReceived, &asyncReceived);
 
@@ -252,13 +256,12 @@
 
     jint retVal = 0;
 
-    if(syncReceived) {
-        retVal |= SYNC_RECEIVED_WHILE_FROZEN;;
-    }
-
-    if(asyncReceived) {
-        retVal |= ASYNC_RECEIVED_WHILE_FROZEN;
-    }
+    // bit 0 of sync_recv goes to bit 0 of retVal
+    retVal |= syncReceived & SYNC_RECEIVED_WHILE_FROZEN;
+    // bit 0 of async_recv goes to bit 1 of retVal
+    retVal |= (asyncReceived << 1) & ASYNC_RECEIVED_WHILE_FROZEN;
+    // bit 1 of sync_recv goes to bit 2 of retVal
+    retVal |= (syncReceived << 1) & TXNS_PENDING_WHILE_FROZEN;
 
     return retVal;
 }
@@ -278,7 +281,7 @@
         /* name, signature, funcPtr */
         {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
         {"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
-        {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
+        {"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
         {"getBinderFreezeInfo", "(I)I",
          (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
         {"getFreezerCheckPath", "()Ljava/lang/String;",
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index aca7cc9..e012dd2 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -436,7 +436,6 @@
     private static final String SYSPROP_START_UPTIME = "sys.system_server.start_uptime";
 
     private Future<?> mZygotePreload;
-    private Future<?> mBlobStoreServiceStart;
 
     private final SystemServerDumper mDumper = new SystemServerDumper();
 
@@ -2251,12 +2250,9 @@
                 t.traceEnd();
             }
 
-            mBlobStoreServiceStart = SystemServerInitThreadPool.submit(() -> {
-                final TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog();
-                traceLog.traceBegin(START_BLOB_STORE_SERVICE);
-                mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS);
-                traceLog.traceEnd();
-            }, START_BLOB_STORE_SERVICE);
+            t.traceBegin(START_BLOB_STORE_SERVICE);
+            mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS);
+            t.traceEnd();
 
             // Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
             t.traceBegin("StartDreamManager");
@@ -2655,9 +2651,6 @@
         mSystemServiceManager.startService(MEDIA_COMMUNICATION_SERVICE_CLASS);
         t.traceEnd();
 
-        ConcurrentUtils.waitForFutureNoInterrupt(mBlobStoreServiceStart,
-                START_BLOB_STORE_SERVICE);
-
         // These are needed to propagate to the runnable below.
         final NetworkManagementService networkManagementF = networkManagement;
         final NetworkStatsService networkStatsF = networkStats;
diff --git a/services/tests/PackageManager/packageinstaller/Android.bp b/services/tests/PackageManager/packageinstaller/Android.bp
new file mode 100644
index 0000000..35d754b
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2021 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "PackageInstallerTests",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.runner",
+        "junit",
+        "kotlin-test",
+        "truth-prebuilt",
+    ],
+    platform_apis: true,
+    test_suites: ["device-tests"],
+}
diff --git a/services/tests/PackageManager/packageinstaller/AndroidManifest.xml b/services/tests/PackageManager/packageinstaller/AndroidManifest.xml
new file mode 100644
index 0000000..d706258
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.packageinstaller.test">
+
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.packageinstaller.test"
+        />
+
+</manifest>
+
diff --git a/services/tests/PackageManager/packageinstaller/AndroidTest.xml b/services/tests/PackageManager/packageinstaller/AndroidTest.xml
new file mode 100644
index 0000000..c39285ff
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+<configuration description="Test module config for PackageInstallerTests">
+    <option name="test-tag" value="PackageInstallerTests" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="PackageInstallerTests.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.packageinstaller.test" />
+    </test>
+</configuration>
diff --git a/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt b/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt
new file mode 100644
index 0000000..d7d2726
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 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.packageinstaller.test
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import androidx.test.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+
+class ExportedComponentTest {
+
+    private val context: Context = InstrumentationRegistry.getContext()
+
+    @Test
+    fun verifyNoExportedReceivers() {
+        val intent = Intent(Intent.ACTION_INSTALL_PACKAGE).apply {
+            data = Uri.parse("content://mockForTest")
+        }
+        val packageInstallers = context.packageManager.queryIntentActivities(intent,
+            PackageManager.MATCH_DEFAULT_ONLY or PackageManager.MATCH_DISABLED_COMPONENTS)
+            .map { it.activityInfo.packageName }
+            .distinct()
+            .map { context.packageManager.getPackageInfo(it, PackageManager.GET_RECEIVERS) }
+
+        assertThat(packageInstallers).isNotEmpty()
+
+        packageInstallers.forEach {
+            val exported = it.receivers.filter { it.exported }
+            assertWithMessage("Receivers should not be exported").that(exported).isEmpty()
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 17a5dcc..3cab5ec 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -28,6 +28,7 @@
     <uses-permission android:name="android.permission.MANAGE_APPOPS"/>
     <uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/>
     <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
     <uses-permission
         android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 4d86c87..85ef8f7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -182,7 +182,14 @@
     }
 
     private void mockDeviceConfigPerformance() {
-        String configString = "mode=2,downscaleFactor=0.5";
+        String configString = "mode=2,downscaleFactor=0.5,useAngle=false";
+        when(DeviceConfig.getProperty(anyString(), anyString()))
+                .thenReturn(configString);
+    }
+
+    // ANGLE will be disabled for most apps, so treat enabling ANGLE as a special case.
+    private void mockDeviceConfigPerformanceEnableAngle() {
+        String configString = "mode=2,downscaleFactor=0.5,useAngle=true";
         when(DeviceConfig.getProperty(anyString(), anyString()))
                 .thenReturn(configString);
     }
@@ -200,7 +207,7 @@
     }
 
     private void mockDeviceConfigInvalid() {
-        String configString = "mode=2,downscaleFactor=0.55";
+        String configString = "";
         when(DeviceConfig.getProperty(anyString(), anyString()))
                 .thenReturn(configString);
     }
@@ -212,7 +219,8 @@
     }
 
     private void mockGameModeOptInAll() throws Exception {
-        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
         Bundle metaDataBundle = new Bundle();
         metaDataBundle.putBoolean(
                 GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
@@ -224,7 +232,8 @@
     }
 
     private void mockGameModeOptInPerformance() throws Exception {
-        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
         Bundle metaDataBundle = new Bundle();
         metaDataBundle.putBoolean(
                 GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
@@ -234,7 +243,8 @@
     }
 
     private void mockGameModeOptInBattery() throws Exception {
-        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
         Bundle metaDataBundle = new Bundle();
         metaDataBundle.putBoolean(
                 GameManagerService.GamePackageConfiguration.METADATA_BATTERY_MODE_ENABLE, true);
@@ -244,7 +254,8 @@
     }
 
     private void mockInterventionAllowDownscaleTrue() throws Exception {
-        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
         Bundle metaDataBundle = new Bundle();
         metaDataBundle.putBoolean(
                 GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, true);
@@ -254,7 +265,8 @@
     }
 
     private void mockInterventionAllowDownscaleFalse() throws Exception {
-        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
         Bundle metaDataBundle = new Bundle();
         metaDataBundle.putBoolean(
                 GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, false);
@@ -263,6 +275,27 @@
                 .thenReturn(applicationInfo);
     }
 
+    private void mockInterventionAllowAngleTrue() throws Exception {
+        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+        Bundle metaDataBundle = new Bundle();
+        metaDataBundle.putBoolean(
+                GameManagerService.GamePackageConfiguration.METADATA_ANGLE_ALLOW_ANGLE, true);
+        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(applicationInfo);
+    }
+
+    private void mockInterventionAllowAngleFalse() throws Exception {
+        final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+                mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+        Bundle metaDataBundle = new Bundle();
+        metaDataBundle.putBoolean(
+                GameManagerService.GamePackageConfiguration.METADATA_ANGLE_ALLOW_ANGLE, false);
+        applicationInfo.metaData = metaDataBundle;
+        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(applicationInfo);
+    }
+
     /**
      * By default game mode is not supported.
      */
@@ -340,11 +373,12 @@
      */
     @Test
     public void testSetGameModePermissionDenied() {
+        mockModifyGameModeGranted();
+        mockDeviceConfigAll();
         GameManagerService gameManagerService = new GameManagerService(mMockContext);
         gameManagerService.onUserStarting(USER_ID_1);
 
         // Update the game mode so we can read back something valid.
-        mockModifyGameModeGranted();
         gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
         assertEquals(GameManager.GAME_MODE_STANDARD,
                 gameManagerService.getGameMode(mPackageName, USER_ID_1));
@@ -427,6 +461,19 @@
         assertEquals(config.getGameModeConfiguration(gameMode).getScaling(), scaling);
     }
 
+    private void checkAngleEnabled(GameManagerService gameManagerService, int gameMode,
+            boolean angleEnabled) {
+        gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+
+        // Validate GamePackageConfiguration returns the correct value.
+        GameManagerService.GamePackageConfiguration config =
+                gameManagerService.getConfig(mPackageName);
+        assertEquals(config.getGameModeConfiguration(gameMode).getUseAngle(), angleEnabled);
+
+        // Validate GameManagerService.getAngleEnabled() returns the correct value.
+        assertEquals(gameManagerService.getAngleEnabled(mPackageName, USER_ID_1), angleEnabled);
+    }
+
     /**
      * Phenotype device config exists, but is only propagating the default value.
      */
@@ -592,6 +639,50 @@
     }
 
     /**
+     * PERFORMANCE game mode is configured through Phenotype. The app hasn't specified any metadata.
+     */
+    @Test
+    public void testInterventionAllowAngleDefault() throws Exception {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        mockDeviceConfigPerformance();
+        mockModifyGameModeGranted();
+        checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, false);
+    }
+
+    /**
+     * PERFORMANCE game mode is configured through Phenotype. The app has opted-out of ANGLE.
+     */
+    @Test
+    public void testInterventionAllowAngleFalse() throws Exception {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        mockDeviceConfigPerformanceEnableAngle();
+        mockInterventionAllowAngleFalse();
+        mockModifyGameModeGranted();
+        checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, false);
+    }
+
+    /**
+     * PERFORMANCE game mode is configured through Phenotype. The app has redundantly specified
+     * the ANGLE metadata default value of "true".
+     */
+    @Test
+    public void testInterventionAllowAngleTrue() throws Exception {
+        mockDeviceConfigPerformanceEnableAngle();
+        mockInterventionAllowAngleTrue();
+
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onUserStarting(USER_ID_1);
+        mockModifyGameModeGranted();
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+                gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+        checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, true);
+    }
+
+    /**
      * PERFORMANCE game mode is configured through Phenotype, but the app has also opted into the
      * same mode. No interventions for this game mode should be available in this case.
      */
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
new file mode 100644
index 0000000..f94377f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2020 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.power;
+
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_VR;
+import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
+
+import static com.android.server.power.ScreenUndimDetector.DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS;
+import static com.android.server.power.ScreenUndimDetector.KEY_KEEP_SCREEN_ON_ENABLED;
+import static com.android.server.power.ScreenUndimDetector.KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS;
+import static com.android.server.power.ScreenUndimDetector.KEY_UNDIMS_REQUIRED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.testables.TestableDeviceConfig;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for {@link com.android.server.power.ScreenUndimDetector}
+ */
+@RunWith(JUnit4.class)
+public class ScreenUndimDetectorTest {
+    private static final List<Integer> ALL_POLICIES =
+            Arrays.asList(POLICY_OFF,
+                    POLICY_DOZE,
+                    POLICY_DIM,
+                    POLICY_BRIGHT,
+                    POLICY_VR);
+
+    @ClassRule
+    public static final TestableContext sContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+    @Rule
+    public TestableDeviceConfig.TestableDeviceConfigRule
+            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+    private ScreenUndimDetector mScreenUndimDetector;
+
+    private final TestClock mClock = new TestClock();
+
+    private static class TestClock extends ScreenUndimDetector.InternalClock {
+        long mCurrentTime = 0;
+        @Override
+        public long getCurrentTime() {
+            return mCurrentTime;
+        }
+
+        public void advanceTime(long millisAdvanced) {
+            mCurrentTime += millisAdvanced;
+        }
+    }
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_UNDIMS_REQUIRED,
+                Integer.toString(1), false /*makeDefault*/);
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS,
+                Long.toString(DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS),
+                false /*makeDefault*/);
+
+        mScreenUndimDetector = new ScreenUndimDetector(mClock);
+        mScreenUndimDetector.systemReady(sContext);
+    }
+
+    @Test
+    public void recordScreenPolicy_disabledByFlag_noop() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_KEEP_SCREEN_ON_ENABLED, Boolean.FALSE.toString(), false /*makeDefault*/);
+
+        setup();
+        mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+        assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+    }
+
+    @Test
+    public void recordScreenPolicy_samePolicy_noop() {
+        for (int policy : ALL_POLICIES) {
+            setup();
+            mScreenUndimDetector.recordScreenPolicy(policy);
+            mScreenUndimDetector.recordScreenPolicy(policy);
+
+            assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+        }
+    }
+
+    @Test
+    public void recordScreenPolicy_dimToBright_extends() {
+        mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+        assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isTrue();
+    }
+
+    @Test
+    public void recordScreenPolicy_otherTransitions_doesNotExtend() {
+        for (int from : ALL_POLICIES) {
+            for (int to : ALL_POLICIES) {
+                if (from == POLICY_DIM && to == POLICY_BRIGHT) {
+                    continue;
+                }
+                setup();
+                mScreenUndimDetector.recordScreenPolicy(from);
+                mScreenUndimDetector.recordScreenPolicy(to);
+
+                assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+                assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+            }
+        }
+    }
+
+    @Test
+    public void recordScreenPolicy_dimToBright_twoUndimsNeeded_extends() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_UNDIMS_REQUIRED,
+                Integer.toString(2), false /*makeDefault*/);
+        mScreenUndimDetector.readValuesFromDeviceConfig();
+
+        mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+        assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+
+        mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+        assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isTrue();
+    }
+
+    @Test
+    public void recordScreenPolicy_dimBrightDimOff_resetsCounter_doesNotExtend() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_UNDIMS_REQUIRED,
+                Integer.toString(2), false /*makeDefault*/);
+        mScreenUndimDetector.readValuesFromDeviceConfig();
+
+        mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_OFF);
+
+        assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+        assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+    }
+
+    @Test
+    public void recordScreenPolicy_undimToOff_resetsCounter() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_UNDIMS_REQUIRED,
+                Integer.toString(2), false /*makeDefault*/);
+        mScreenUndimDetector.readValuesFromDeviceConfig();
+
+        mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_OFF);
+
+        assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+        assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+    }
+
+    @Test
+    public void recordScreenPolicy_undimOffUndim_doesNotExtend() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_UNDIMS_REQUIRED,
+                Integer.toString(2), false /*makeDefault*/);
+        mScreenUndimDetector.readValuesFromDeviceConfig();
+
+        // undim
+        mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+        // off
+        mScreenUndimDetector.recordScreenPolicy(POLICY_OFF);
+        // second undim
+        mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+        assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+        assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(1);
+    }
+
+    @Test
+    public void recordScreenPolicy_dimToBright_tooFarApart_doesNotExtend() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_UNDIMS_REQUIRED,
+                Integer.toString(2), false /*makeDefault*/);
+        mScreenUndimDetector.readValuesFromDeviceConfig();
+
+        mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+        mClock.advanceTime(DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS + 5);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+        mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+        assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+        assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(1);
+    }
+
+    @Test
+    public void recordScreenPolicy_dimToNonBright_resets() {
+        for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE, POLICY_VR)) {
+            setup();
+            mScreenUndimDetector.mUndimCounter = 1;
+            mScreenUndimDetector.mUndimCounterStartedMillis = 123;
+            mScreenUndimDetector.mWakeLock.acquire();
+
+            mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+            mScreenUndimDetector.recordScreenPolicy(to);
+
+            assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+            assertThat(mScreenUndimDetector.mUndimCounterStartedMillis).isEqualTo(0);
+            assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+        }
+
+    }
+
+    @Test
+    public void recordScreenPolicy_brightToNonDim_resets() {
+        for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE, POLICY_VR)) {
+            setup();
+            mScreenUndimDetector.mUndimCounter = 1;
+            mScreenUndimDetector.mUndimCounterStartedMillis = 123;
+            mScreenUndimDetector.mWakeLock.acquire();
+
+            mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+            mScreenUndimDetector.recordScreenPolicy(to);
+
+            assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+            assertThat(mScreenUndimDetector.mUndimCounterStartedMillis).isEqualTo(0);
+            assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+        }
+    }
+
+    @Test
+    public void recordScreenPolicy_otherTransitions_doesNotReset() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_UNDIMS_REQUIRED,
+                Integer.toString(3),
+                false /*makeDefault*/);
+        mScreenUndimDetector.readValuesFromDeviceConfig();
+
+        for (int from : ALL_POLICIES) {
+            for (int to : ALL_POLICIES) {
+                if (from == POLICY_DIM && to != POLICY_BRIGHT) {
+                    continue;
+                }
+                if (from == POLICY_BRIGHT && to != POLICY_DIM) {
+                    continue;
+                }
+                mScreenUndimDetector.mCurrentScreenPolicy = POLICY_OFF;
+                mScreenUndimDetector.mUndimCounter = 1;
+                mScreenUndimDetector.mUndimCounterStartedMillis =
+                        SystemClock.currentThreadTimeMillis();
+
+                mScreenUndimDetector.recordScreenPolicy(from);
+                mScreenUndimDetector.recordScreenPolicy(to);
+
+                assertThat(mScreenUndimDetector.mUndimCounter).isNotEqualTo(0);
+                assertThat(mScreenUndimDetector.mUndimCounterStartedMillis).isNotEqualTo(0);
+            }
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 53483f6..a49afc7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -18,13 +18,18 @@
 
 import static android.app.WallpaperManager.COMMAND_REAPPLY;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.os.FileObserver.CLOSE_WRITE;
+import static android.os.UserHandle.USER_SYSTEM;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER;
+import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER_CROP;
 
 import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
@@ -34,6 +39,7 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeThat;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.reset;
@@ -41,6 +47,7 @@
 
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.WallpaperColors;
 import android.app.WallpaperManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -49,8 +56,10 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ServiceInfo;
+import android.graphics.Color;
 import android.hardware.display.DisplayManager;
-import android.os.UserHandle;
+import android.os.RemoteException;
+import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.service.wallpaper.IWallpaperConnection;
 import android.service.wallpaper.IWallpaperEngine;
@@ -143,7 +152,6 @@
         sContext.getTestablePermissions().setPermission(
                 android.Manifest.permission.SET_WALLPAPER,
                 PackageManager.PERMISSION_GRANTED);
-        doNothing().when(sContext).sendBroadcastAsUser(any(), any());
 
         //Wallpaper components
         sWallpaperService = mock(IWallpaperConnection.Stub.class);
@@ -180,6 +188,7 @@
         MockitoAnnotations.initMocks(this);
 
         sContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+        doNothing().when(sContext).sendBroadcastAsUser(any(), any());
 
         final Display mockDisplay = mock(Display.class);
         doReturn(DISPLAY_SIZE_DIMENSION).when(mockDisplay).getMaximumSizeDimension();
@@ -242,13 +251,13 @@
      */
     @Test
     public void testDataCorrectAfterBoot() {
-        mService.switchUser(UserHandle.USER_SYSTEM, null);
+        mService.switchUser(USER_SYSTEM, null);
 
         final WallpaperData fallbackData = mService.mFallbackWallpaper;
         assertEquals("Fallback wallpaper component should be ImageWallpaper.",
                 sImageWallpaperComponentName, fallbackData.wallpaperComponent);
 
-        verifyLastWallpaperData(UserHandle.USER_SYSTEM, sDefaultWallpaperComponent);
+        verifyLastWallpaperData(USER_SYSTEM, sDefaultWallpaperComponent);
         verifyDisplayData();
     }
 
@@ -261,7 +270,7 @@
         assumeThat(sDefaultWallpaperComponent,
                 not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
 
-        final int testUserId = UserHandle.USER_SYSTEM;
+        final int testUserId = USER_SYSTEM;
         mService.switchUser(testUserId, null);
         verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
         verifyCurrentSystemData(testUserId);
@@ -281,7 +290,7 @@
      */
     @Test
     public void testSetCurrentComponent() throws Exception {
-        final int testUserId = UserHandle.USER_SYSTEM;
+        final int testUserId = USER_SYSTEM;
         mService.switchUser(testUserId, null);
         verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
         verifyCurrentSystemData(testUserId);
@@ -387,6 +396,42 @@
         assertEquals(systemWallpaperData.primaryColors, shouldMatchSystem.primaryColors);
     }
 
+    @Test
+    public void testWallpaperManagerCallbackInRightOrder() throws RemoteException {
+        WallpaperData wallpaper = new WallpaperData(
+                USER_SYSTEM, mService.getWallpaperDir(USER_SYSTEM), WALLPAPER, WALLPAPER_CROP);
+        wallpaper.primaryColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+
+        spyOn(wallpaper);
+        doReturn(wallpaper).when(mService).getWallpaperSafeLocked(wallpaper.userId, FLAG_SYSTEM);
+        doNothing().when(mService).switchWallpaper(any(), any());
+        doReturn(true).when(mService)
+                .bindWallpaperComponentLocked(any(), anyBoolean(), anyBoolean(), any(), any());
+        doNothing().when(mService).saveSettingsLocked(wallpaper.userId);
+        doNothing().when(mService).generateCrop(wallpaper);
+
+        // timestamps of {ACTION_WALLPAPER_CHANGED, onWallpaperColorsChanged}
+        final long[] timestamps = new long[2];
+        doAnswer(invocation -> timestamps[0] = SystemClock.elapsedRealtime())
+                .when(sContext).sendBroadcastAsUser(any(), any());
+        doAnswer(invocation -> timestamps[1] = SystemClock.elapsedRealtime())
+                .when(mService).notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM);
+
+        assertNull(wallpaper.wallpaperObserver);
+        mService.switchUser(wallpaper.userId, null);
+        assertNotNull(wallpaper.wallpaperObserver);
+        // We will call onEvent directly, so stop watching the file.
+        wallpaper.wallpaperObserver.stopWatching();
+
+        spyOn(wallpaper.wallpaperObserver);
+        doReturn(wallpaper).when(wallpaper.wallpaperObserver).dataForEvent(true, false);
+        wallpaper.wallpaperObserver.onEvent(CLOSE_WRITE, WALLPAPER);
+
+        // ACTION_WALLPAPER_CHANGED should be invoked before onWallpaperColorsChanged.
+        assertTrue(timestamps[1] > timestamps[0]);
+    }
+
     // Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
     // non-current user must not bind to wallpaper service.
     private void verifyNoConnectionBeforeLastUser(int lastUserId) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 2892bf5..b3f7587 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -74,6 +74,7 @@
 public class AuthSessionTest {
 
     private static final String TEST_PACKAGE = "test_package";
+    private static final long TEST_REQUEST_ID = 22;
 
     @Mock private Context mContext;
     @Mock private ITrustManager mTrustManager;
@@ -112,6 +113,7 @@
         final AuthSession session = createAuthSession(mSensors,
                 false /* checkDevicePolicyManager */,
                 Authenticators.BIOMETRIC_STRONG,
+                TEST_REQUEST_ID,
                 0 /* operationId */,
                 0 /* userId */);
 
@@ -133,6 +135,7 @@
         final AuthSession session = createAuthSession(mSensors,
                 false /* checkDevicePolicyManager */,
                 Authenticators.BIOMETRIC_STRONG,
+                TEST_REQUEST_ID,
                 operationId,
                 userId);
         assertEquals(mSensors.size(), session.mPreAuthInfo.eligibleSensors.size());
@@ -153,6 +156,7 @@
                     eq(userId),
                     eq(mSensorReceiver),
                     eq(TEST_PACKAGE),
+                    eq(TEST_REQUEST_ID),
                     eq(sensor.getCookie()),
                     anyBoolean() /* allowBackgroundAuthentication */);
         }
@@ -185,6 +189,33 @@
     }
 
     @Test
+    public void testCancelReducesAppetiteForCookies() throws Exception {
+        setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
+                mock(IBiometricAuthenticator.class));
+        setupFingerprint(1 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
+
+        final AuthSession session = createAuthSession(mSensors,
+                false /* checkDevicePolicyManager */,
+                Authenticators.BIOMETRIC_STRONG,
+                TEST_REQUEST_ID,
+                44 /* operationId */,
+                2 /* userId */);
+
+        session.goToInitialState();
+
+        for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+            assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState());
+        }
+
+        session.onCancelAuthSession(false /* force */);
+
+        for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+            session.onCookieReceived(sensor.getCookie());
+            assertEquals(BiometricSensor.STATE_CANCELING, sensor.getSensorState());
+        }
+    }
+
+    @Test
     public void testMultiAuth_singleSensor_fingerprintSensorStartsAfterDialogAnimationCompletes()
             throws Exception {
         setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
@@ -212,6 +243,7 @@
         final AuthSession session = createAuthSession(mSensors,
                 false /* checkDevicePolicyManager */,
                 Authenticators.BIOMETRIC_STRONG,
+                TEST_REQUEST_ID,
                 operationId,
                 userId);
         assertEquals(mSensors.size(), session.mPreAuthInfo.eligibleSensors.size());
@@ -238,7 +270,7 @@
         // fingerprint sensor does not start even if all cookies are received
         assertEquals(STATE_AUTH_STARTED, session.getState());
         verify(mStatusBarService).showAuthenticationDialog(any(), any(), any(),
-                anyBoolean(), anyBoolean(), anyInt(), any(), anyLong(), anyInt());
+                anyBoolean(), anyBoolean(), anyInt(), anyLong(), any(), anyLong(), anyInt());
 
         // Notify AuthSession that the UI is shown. Then, fingerprint sensor should be started.
         session.onDialogAnimatedIn();
@@ -277,6 +309,7 @@
         final AuthSession session = createAuthSession(mSensors,
                 false /* checkDevicePolicyManager */,
                 Authenticators.BIOMETRIC_STRONG,
+                TEST_REQUEST_ID,
                 0 /* operationId */,
                 0 /* userId */);
 
@@ -285,7 +318,8 @@
 
         sessionConsumer.accept(session);
 
-        verify(faceAuthenticator).cancelAuthenticationFromService(eq(mToken), eq(TEST_PACKAGE));
+        verify(faceAuthenticator).cancelAuthenticationFromService(
+                eq(mToken), eq(TEST_PACKAGE), eq(TEST_REQUEST_ID));
     }
 
     private PreAuthInfo createPreAuthInfo(List<BiometricSensor> sensors, int userId,
@@ -302,14 +336,14 @@
 
     private AuthSession createAuthSession(List<BiometricSensor> sensors,
             boolean checkDevicePolicyManager, @Authenticators.Types int authenticators,
-            long operationId, int userId) throws RemoteException {
+            long requestId, long operationId, int userId) throws RemoteException {
 
         final PromptInfo promptInfo = createPromptInfo(authenticators);
 
         final PreAuthInfo preAuthInfo = createPreAuthInfo(sensors, userId, promptInfo,
                 checkDevicePolicyManager);
         return new AuthSession(mContext, mStatusBarService, mSysuiReceiver, mKeyStore,
-                mRandom, mClientDeathReceiver, preAuthInfo, mToken, operationId, userId,
+                mRandom, mClientDeathReceiver, preAuthInfo, mToken, requestId, operationId, userId,
                 mSensorReceiver, mClientReceiver, TEST_PACKAGE, promptInfo,
                 false /* debugEnabled */, mFingerprintSensorProps);
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 7c7afb7..69d8e89 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -85,6 +85,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Random;
+import java.util.concurrent.atomic.AtomicLong;
 
 @Presubmit
 @SmallTest
@@ -93,6 +94,7 @@
     private static final String TAG = "BiometricServiceTest";
 
     private static final String TEST_PACKAGE_NAME = "test_package";
+    private static final long TEST_REQUEST_ID = 44;
 
     private static final String ERROR_HW_UNAVAILABLE = "hw_unavailable";
     private static final String ERROR_NOT_RECOGNIZED = "not_recognized";
@@ -151,6 +153,7 @@
                 .thenReturn(mock(BiometricStrengthController.class));
         when(mInjector.getTrustManager()).thenReturn(mTrustManager);
         when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager);
+        when(mInjector.getRequestGenerator()).thenReturn(new AtomicLong(TEST_REQUEST_ID - 1));
 
         when(mResources.getString(R.string.biometric_error_hw_unavailable))
                 .thenReturn(ERROR_HW_UNAVAILABLE);
@@ -215,8 +218,7 @@
                 mBiometricService.mCurrentAuthSession.getState());
 
         verify(mBiometricService.mCurrentAuthSession.mPreAuthInfo.eligibleSensors.get(0).impl)
-                .cancelAuthenticationFromService(any(),
-                        any());
+                .cancelAuthenticationFromService(any(), any(), anyLong());
 
         // Simulate ERROR_CANCELED received from HAL
         mBiometricService.mBiometricSensorReceiver.onError(
@@ -272,8 +274,9 @@
                 eq(true) /* credentialAllowed */,
                 anyBoolean() /* requireConfirmation */,
                 anyInt() /* userId */,
+                anyLong() /* operationId */,
                 eq(TEST_PACKAGE_NAME),
-                anyLong() /* sessionId */,
+                eq(TEST_REQUEST_ID),
                 eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
     }
 
@@ -357,8 +360,9 @@
                 eq(false) /* credentialAllowed */,
                 eq(false) /* requireConfirmation */,
                 anyInt() /* userId */,
+                anyLong() /* operationId */,
                 eq(TEST_PACKAGE_NAME),
-                anyLong() /* sessionId */,
+                eq(TEST_REQUEST_ID),
                 eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
     }
 
@@ -467,6 +471,7 @@
                 anyInt() /* userId */,
                 any(IBiometricSensorReceiver.class),
                 anyString() /* opPackageName */,
+                eq(TEST_REQUEST_ID),
                 cookieCaptor.capture() /* cookie */,
                 anyBoolean() /* allowBackgroundAuthentication */);
 
@@ -488,8 +493,9 @@
                 eq(false) /* credentialAllowed */,
                 anyBoolean() /* requireConfirmation */,
                 anyInt() /* userId */,
+                anyLong() /* operationId */,
                 eq(TEST_PACKAGE_NAME),
-                anyLong() /* sessionId */,
+                eq(TEST_REQUEST_ID),
                 eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
 
         // Hardware authenticated
@@ -543,8 +549,9 @@
                 eq(true) /* credentialAllowed */,
                 anyBoolean() /* requireConfirmation */,
                 anyInt() /* userId */,
+                anyLong() /* operationId */,
                 eq(TEST_PACKAGE_NAME),
-                anyLong() /* sessionId */,
+                eq(TEST_REQUEST_ID),
                 eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
     }
 
@@ -705,8 +712,9 @@
                 anyBoolean() /* credentialAllowed */,
                 anyBoolean() /* requireConfirmation */,
                 anyInt() /* userId */,
+                anyLong() /* operationId */,
                 anyString(),
-                anyLong() /* sessionId */,
+                anyLong() /* requestId */,
                 eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
     }
 
@@ -805,8 +813,9 @@
                 eq(true) /* credentialAllowed */,
                 anyBoolean() /* requireConfirmation */,
                 anyInt() /* userId */,
+                anyLong() /* operationId */,
                 eq(TEST_PACKAGE_NAME),
-                anyLong() /* sessionId */,
+                eq(TEST_REQUEST_ID),
                 eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
     }
 
@@ -885,8 +894,9 @@
                 eq(true) /* credentialAllowed */,
                 anyBoolean() /* requireConfirmation */,
                 anyInt() /* userId */,
+                anyLong() /* operationId */,
                 eq(TEST_PACKAGE_NAME),
-                anyLong() /* sessionId */,
+                eq(TEST_REQUEST_ID),
                 eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
     }
 
@@ -1030,8 +1040,7 @@
                 eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
                 eq(0 /* vendorCode */));
         verify(mBiometricService.mSensors.get(0).impl).cancelAuthenticationFromService(
-                any(),
-                any());
+                any(), any(), anyLong());
         assertNull(mBiometricService.mCurrentAuthSession);
     }
 
@@ -1051,7 +1060,7 @@
         waitForIdle();
 
         verify(mBiometricService.mSensors.get(0).impl)
-                .cancelAuthenticationFromService(any(), any());
+                .cancelAuthenticationFromService(any(), any(), anyLong());
     }
 
     @Test
@@ -1071,7 +1080,7 @@
         waitForIdle();
 
         verify(mBiometricService.mSensors.get(0).impl)
-                .cancelAuthenticationFromService(any(), any());
+                .cancelAuthenticationFromService(any(), any(), anyLong());
     }
 
     @Test
@@ -1088,7 +1097,7 @@
         waitForIdle();
 
         verify(mBiometricService.mSensors.get(0).impl)
-                .cancelAuthenticationFromService(any(), any());
+                .cancelAuthenticationFromService(any(), any(), anyLong());
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_FACE),
                 eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
@@ -1126,7 +1135,7 @@
                 false /* requireConfirmation */, null /* authenticators */);
 
         mBiometricService.mImpl.cancelAuthentication(mBiometricService.mCurrentAuthSession.mToken,
-                TEST_PACKAGE_NAME);
+                TEST_PACKAGE_NAME, TEST_REQUEST_ID);
         waitForIdle();
 
         // Pretend that the HAL has responded to cancel with ERROR_CANCELED
@@ -1353,8 +1362,8 @@
         int authenticators = Authenticators.BIOMETRIC_STRONG;
         assertEquals(BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
                 invokeCanAuthenticate(mBiometricService, authenticators));
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                authenticators);
+        long requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+                false /* requireConfirmation */, authenticators);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_FINGERPRINT),
@@ -1366,7 +1375,7 @@
         authenticators = Authenticators.BIOMETRIC_WEAK;
         assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
                 invokeCanAuthenticate(mBiometricService, authenticators));
-        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+        requestId = invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */,
                 authenticators);
         waitForIdle();
@@ -1377,8 +1386,9 @@
                 eq(false) /* credentialAllowed */,
                 anyBoolean() /* requireConfirmation */,
                 anyInt() /* userId */,
+                anyLong() /* operationId */,
                 eq(TEST_PACKAGE_NAME),
-                anyLong() /* sessionId */,
+                eq(requestId),
                 eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
 
         // Requesting strong and credential, when credential is setup
@@ -1387,7 +1397,7 @@
         when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
         assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
                 invokeCanAuthenticate(mBiometricService, authenticators));
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+        requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */,
                 authenticators);
         waitForIdle();
@@ -1399,8 +1409,9 @@
                 eq(true) /* credentialAllowed */,
                 anyBoolean() /* requireConfirmation */,
                 anyInt() /* userId */,
+                anyLong() /* operationId */,
                 eq(TEST_PACKAGE_NAME),
-                anyLong() /* sessionId */,
+                eq(requestId),
                 eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
 
         // Un-downgrading the authenticator allows successful strong auth
@@ -1414,7 +1425,7 @@
         authenticators = Authenticators.BIOMETRIC_STRONG;
         assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
                 invokeCanAuthenticate(mBiometricService, authenticators));
-        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+        requestId = invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, authenticators);
         waitForIdle();
         verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
@@ -1424,8 +1435,9 @@
                 eq(false) /* credentialAllowed */,
                 anyBoolean() /* requireConfirmation */,
                 anyInt() /* userId */,
+                anyLong() /* operationId */,
                 eq(TEST_PACKAGE_NAME),
-                anyLong() /* sessionId */,
+                eq(requestId),
                 eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
     }
 
@@ -1617,11 +1629,12 @@
         mBiometricService.mStatusBarService = mock(IStatusBarService.class);
     }
 
-    private void invokeAuthenticateAndStart(IBiometricService.Stub service,
+    private long invokeAuthenticateAndStart(IBiometricService.Stub service,
             IBiometricServiceReceiver receiver, boolean requireConfirmation,
             Integer authenticators) throws Exception {
         // Request auth, creates a pending session
-        invokeAuthenticate(service, receiver, requireConfirmation, authenticators);
+        final long requestId = invokeAuthenticate(
+                service, receiver, requireConfirmation, authenticators);
         waitForIdle();
 
         startPendingAuthSession(mBiometricService);
@@ -1629,6 +1642,8 @@
 
         assertNotNull(mBiometricService.mCurrentAuthSession);
         assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+
+        return requestId;
     }
 
     private static void startPendingAuthSession(BiometricService service) throws Exception {
@@ -1644,10 +1659,10 @@
         service.mImpl.onReadyForAuthentication(cookie);
     }
 
-    private static void invokeAuthenticate(IBiometricService.Stub service,
+    private static long invokeAuthenticate(IBiometricService.Stub service,
             IBiometricServiceReceiver receiver, boolean requireConfirmation,
             Integer authenticators) throws Exception {
-        service.authenticate(
+        return service.authenticate(
                 new Binder() /* token */,
                 0 /* operationId */,
                 0 /* userId */,
@@ -1657,9 +1672,9 @@
                         false /* checkDevicePolicy */));
     }
 
-    private static void invokeAuthenticateForWorkApp(IBiometricService.Stub service,
+    private static long invokeAuthenticateForWorkApp(IBiometricService.Stub service,
             IBiometricServiceReceiver receiver, Integer authenticators) throws Exception {
-        service.authenticate(
+        return service.authenticate(
                 new Binder() /* token */,
                 0 /* operationId */,
                 0 /* userId */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index a41f79e..e3e3900 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -40,6 +40,7 @@
 import android.testing.TestableContext;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
@@ -193,7 +194,7 @@
         // Request it to be canceled. The operation can be canceled immediately, and the scheduler
         // should go back to idle, since in this case the framework has not even requested the HAL
         // to authenticate yet.
-        mScheduler.cancelAuthenticationOrDetection(mToken);
+        mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
         assertNull(mScheduler.mCurrentOperation);
     }
 
@@ -303,7 +304,7 @@
                 mScheduler.mPendingOperations.getFirst().mState);
 
         // Request cancel before the authentication client has started
-        mScheduler.cancelAuthenticationOrDetection(mToken);
+        mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
         waitForIdle();
         assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
                 mScheduler.mPendingOperations.getFirst().mState);
@@ -318,6 +319,107 @@
     }
 
     @Test
+    public void testCancels_whenAuthRequestIdNotSet() {
+        testCancelsWhenRequestId(null /* requestId */, 2, true /* started */);
+    }
+
+    @Test
+    public void testCancels_whenAuthRequestIdNotSet_notStarted() {
+        testCancelsWhenRequestId(null /* requestId */, 2, false /* started */);
+    }
+
+    @Test
+    public void testCancels_whenAuthRequestIdMatches() {
+        testCancelsWhenRequestId(200L, 200, true /* started */);
+    }
+
+    @Test
+    public void testCancels_whenAuthRequestIdMatches_noStarted() {
+        testCancelsWhenRequestId(200L, 200, false /* started */);
+    }
+
+    @Test
+    public void testDoesNotCancel_whenAuthRequestIdMismatched() {
+        testCancelsWhenRequestId(10L, 20, true /* started */);
+    }
+
+    @Test
+    public void testDoesNotCancel_whenAuthRequestIdMismatched_notStarted() {
+        testCancelsWhenRequestId(10L, 20, false /* started */);
+    }
+
+    private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId,
+            boolean started) {
+        final boolean matches = requestId == null || requestId == cancelRequestId;
+        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+        final TestAuthenticationClient client = new TestAuthenticationClient(
+                mContext, lazyDaemon, mToken, callback);
+        if (requestId != null) {
+            client.setRequestId(requestId);
+        }
+
+        mScheduler.scheduleClientMonitor(client);
+        if (started) {
+            mScheduler.startPreparedClient(client.getCookie());
+        }
+        waitForIdle();
+        mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId);
+        waitForIdle();
+
+        assertEquals(matches && started ? 1 : 0, client.mNumCancels);
+
+        if (matches) {
+            if (started) {
+                assertEquals(Operation.STATE_STARTED_CANCELING,
+                        mScheduler.mCurrentOperation.mState);
+            }
+        } else {
+            if (started) {
+                assertEquals(Operation.STATE_STARTED,
+                        mScheduler.mCurrentOperation.mState);
+            } else {
+                assertEquals(Operation.STATE_WAITING_FOR_COOKIE,
+                        mScheduler.mCurrentOperation.mState);
+            }
+        }
+    }
+
+    @Test
+    public void testCancelsPending_whenAuthRequestIdsSet() {
+        final long requestId1 = 10;
+        final long requestId2 = 20;
+        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+        final TestAuthenticationClient client1 = new TestAuthenticationClient(
+                mContext, lazyDaemon, mToken, callback);
+        client1.setRequestId(requestId1);
+        final TestAuthenticationClient client2 = new TestAuthenticationClient(
+                mContext, lazyDaemon, mToken, callback);
+        client2.setRequestId(requestId2);
+
+        mScheduler.scheduleClientMonitor(client1);
+        mScheduler.scheduleClientMonitor(client2);
+        mScheduler.startPreparedClient(client1.getCookie());
+        waitForIdle();
+        mScheduler.cancelAuthenticationOrDetection(mToken, 9999);
+        waitForIdle();
+
+        assertEquals(Operation.STATE_STARTED,
+                mScheduler.mCurrentOperation.mState);
+        assertEquals(Operation.STATE_WAITING_IN_QUEUE,
+                mScheduler.mPendingOperations.getFirst().mState);
+
+        mScheduler.cancelAuthenticationOrDetection(mToken, requestId2);
+        waitForIdle();
+
+        assertEquals(Operation.STATE_STARTED,
+                mScheduler.mCurrentOperation.mState);
+        assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
+                mScheduler.mPendingOperations.getFirst().mState);
+    }
+
+    @Test
     public void testInterruptPrecedingClients_whenExpected() {
         final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class,
                 withSettings().extraInterfaces(Interruptable.class));
@@ -377,12 +479,10 @@
 
         @Override
         protected void stopHalOperation() {
-
         }
 
         @Override
         protected void startHalOperation() {
-
         }
 
         @Override
@@ -397,6 +497,7 @@
     }
 
     private static class TestAuthenticationClient extends AuthenticationClient<Object> {
+        int mNumCancels = 0;
 
         public TestAuthenticationClient(@NonNull Context context,
                 @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
@@ -428,6 +529,11 @@
         public boolean wasUserDetected() {
             return false;
         }
+
+        public void cancel() {
+            mNumCancels++;
+            super.cancel();
+        }
     }
 
     private static class TestClientMonitor2 extends TestClientMonitor {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
new file mode 100644
index 0000000..09b5c5c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@Presubmit
+@SmallTest
+public class CompositeCallbackTest {
+
+    @Test
+    public void testNullCallback() {
+        BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
+        BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
+        BaseClientMonitor.Callback callback3 = null;
+
+        BaseClientMonitor.CompositeCallback callback = new BaseClientMonitor.CompositeCallback(
+                callback1, callback2, callback3);
+
+        BaseClientMonitor clientMonitor = mock(BaseClientMonitor.class);
+
+        callback.onClientStarted(clientMonitor);
+        verify(callback1).onClientStarted(eq(clientMonitor));
+        verify(callback2).onClientStarted(eq(clientMonitor));
+
+        callback.onClientFinished(clientMonitor, true /* success */);
+        verify(callback1).onClientFinished(eq(clientMonitor), eq(true));
+        verify(callback2).onClientFinished(eq(clientMonitor), eq(true));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java
new file mode 100644
index 0000000..38e8dfa
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors.fingerprint;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.fingerprint.FingerprintStateListener;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.EnrollClient;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+public class FingerprintStateCallbackTest {
+
+    private FingerprintStateCallback mCallback;
+
+    @Mock
+    FingerprintStateListener mFingerprintStateListener;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mCallback = new FingerprintStateCallback();
+        mCallback.registerFingerprintStateListener(mFingerprintStateListener);
+    }
+
+    @Test
+    public void testNoEnrollmentsToEnrollments_callbackNotified() {
+        testEnrollmentCallback(true /* changed */, true /* isNowEnrolled */,
+                true /* expectCallback */, true /* expectedCallbackValue */);
+    }
+
+    @Test
+    public void testEnrollmentsToNoEnrollments_callbackNotified() {
+        testEnrollmentCallback(true /* changed */, false /* isNowEnrolled */,
+                true /* expectCallback */, false /* expectedCallbackValue */);
+    }
+
+    @Test
+    public void testEnrollmentsToEnrollments_callbackNotNotified() {
+        testEnrollmentCallback(false /* changed */, true /* isNowEnrolled */,
+                false /* expectCallback */, false /* expectedCallbackValue */);
+    }
+
+    private void testEnrollmentCallback(boolean changed, boolean isNowEnrolled,
+            boolean expectCallback, boolean expectedCallbackValue) {
+        EnrollClient<?> client = mock(EnrollClient.class);
+
+        final int userId = 10;
+        final int sensorId = 100;
+
+        when(client.hasEnrollmentStateChanged()).thenReturn(changed);
+        when(client.hasEnrollments()).thenReturn(isNowEnrolled);
+        when(client.getTargetUserId()).thenReturn(userId);
+        when(client.getSensorId()).thenReturn(sensorId);
+
+        mCallback.onClientFinished(client, true /* success */);
+        if (expectCallback) {
+            verify(mFingerprintStateListener).onEnrollmentsChanged(eq(userId), eq(sensorId),
+                    eq(expectedCallbackValue));
+        } else {
+            verify(mFingerprintStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
+                    anyBoolean());
+        }
+    }
+
+    @Test
+    public void testAuthentication_enrollmentCallbackNeverNotified() {
+        AuthenticationClient<?> client = mock(AuthenticationClient.class);
+        mCallback.onClientFinished(client, true /* success */);
+        verify(mFingerprintStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
+                anyBoolean());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 35c37ef..b51918e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -37,6 +37,7 @@
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import org.junit.Before;
@@ -58,6 +59,8 @@
     private UserManager mUserManager;
     @Mock
     private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
+    @Mock
+    private FingerprintStateCallback mFingerprintStateCallback;
 
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -87,8 +90,8 @@
 
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
-        mFingerprintProvider = new TestableFingerprintProvider(mContext, mSensorProps, TAG,
-                mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+        mFingerprintProvider = new TestableFingerprintProvider(mContext, mFingerprintStateCallback,
+                mSensorProps, TAG, mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
     }
 
     @SuppressWarnings("rawtypes")
@@ -133,11 +136,12 @@
 
     private static class TestableFingerprintProvider extends FingerprintProvider {
         public TestableFingerprintProvider(@NonNull Context context,
+                @NonNull FingerprintStateCallback fingerprintStateCallback,
                 @NonNull SensorProps[] props,
                 @NonNull String halInstanceName,
                 @NonNull LockoutResetDispatcher lockoutResetDispatcher,
                 @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-            super(context, props, halInstanceName, lockoutResetDispatcher,
+            super(context, fingerprintStateCallback, props, halInstanceName, lockoutResetDispatcher,
                     gestureAvailabilityDispatcher);
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
index 0a0dcc9..f6b9209 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
@@ -41,6 +41,7 @@
 import com.android.internal.R;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -67,6 +68,8 @@
     Fingerprint21.HalResultController mHalResultController;
     @Mock
     private BiometricScheduler mScheduler;
+    @Mock
+    private FingerprintStateCallback mFingerprintStateCallback;
 
     private LockoutResetDispatcher mLockoutResetDispatcher;
     private Fingerprint21 mFingerprint21;
@@ -96,8 +99,9 @@
                         componentInfo, FingerprintSensorProperties.TYPE_UNKNOWN,
                         resetLockoutRequiresHardwareAuthToken);
 
-        mFingerprint21 = new TestableFingerprint21(mContext, sensorProps, mScheduler,
-                new Handler(Looper.getMainLooper()), mLockoutResetDispatcher, mHalResultController);
+        mFingerprint21 = new TestableFingerprint21(mContext, mFingerprintStateCallback, sensorProps,
+                mScheduler, new Handler(Looper.getMainLooper()), mLockoutResetDispatcher,
+                mHalResultController);
     }
 
     @Test
@@ -118,11 +122,13 @@
     private static class TestableFingerprint21 extends Fingerprint21 {
 
         TestableFingerprint21(@NonNull Context context,
+                @NonNull FingerprintStateCallback fingerprintStateCallback,
                 @NonNull FingerprintSensorPropertiesInternal sensorProps,
                 @NonNull BiometricScheduler scheduler, @NonNull Handler handler,
                 @NonNull LockoutResetDispatcher lockoutResetDispatcher,
                 @NonNull HalResultController controller) {
-            super(context, sensorProps, scheduler, handler, lockoutResetDispatcher, controller);
+            super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+                    lockoutResetDispatcher, controller);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 1ac28ab..4564296 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -56,7 +56,11 @@
 import android.hardware.display.DisplayManagerInternal.RefreshRateRange;
 import android.hardware.fingerprint.IUdfpsHbmListener;
 import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
 import android.os.Looper;
+import android.os.RemoteException;
+import android.os.Temperature;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
@@ -116,6 +120,8 @@
     public SensorManagerInternal mSensorManagerInternalMock;
     @Mock
     public DisplayManagerInternal mDisplayManagerInternalMock;
+    @Mock
+    public IThermalService mThermalServiceMock;
 
     @Before
     public void setUp() throws Exception {
@@ -124,6 +130,7 @@
         final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
         when(mContext.getContentResolver()).thenReturn(resolver);
         mInjector = spy(new FakesInjector());
+        when(mInjector.getThermalService()).thenReturn(mThermalServiceMock);
         mHandler = new Handler(Looper.getMainLooper());
 
         LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
@@ -1547,12 +1554,52 @@
         assertNull(vote);
     }
 
+    @Test
+    public void testSkinTemperature() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<IThermalEventListener> thermalEventListener =
+                ArgumentCaptor.forClass(IThermalEventListener.class);
+
+        verify(mThermalServiceMock).registerThermalEventListenerWithType(
+            thermalEventListener.capture(), eq(Temperature.TYPE_SKIN));
+        final IThermalEventListener listener = thermalEventListener.getValue();
+
+        // Verify that there is no skin temperature vote initially.
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+        assertNull(vote);
+
+        // Set the skin temperature to critical and verify that we added a vote.
+        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+        assertVoteForRefreshRateRange(vote, 0f, 60.f);
+
+        // Set the skin temperature to severe and verify that the vote is gone.
+        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_SEVERE));
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+        assertNull(vote);
+    }
+
+    private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
+        return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
+    }
+
     private void assertVoteForRefreshRate(Vote vote, float refreshRate) {
         assertThat(vote).isNotNull();
         final RefreshRateRange expectedRange = new RefreshRateRange(refreshRate, refreshRate);
         assertThat(vote.refreshRateRange).isEqualTo(expectedRange);
     }
 
+    private void assertVoteForRefreshRateRange(
+            Vote vote, float refreshRateLow, float refreshRateHigh) {
+        assertThat(vote).isNotNull();
+        final RefreshRateRange expectedRange =
+                new RefreshRateRange(refreshRateLow, refreshRateHigh);
+        assertThat(vote.refreshRateRange).isEqualTo(expectedRange);
+    }
+
     public static class FakeDeviceConfig extends FakeDeviceConfigInterface {
         @Override
         public String getProperty(String namespace, String name) {
@@ -1748,6 +1795,11 @@
             return false;
         }
 
+        @Override
+        public IThermalService getThermalService() {
+            return null;
+        }
+
         void notifyPeakRefreshRateChanged() {
             if (mPeakRefreshRateObserver != null) {
                 mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/,
diff --git a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
index 5012ca9..6e3f754 100644
--- a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
@@ -210,7 +210,7 @@
         @Override
         Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
                 SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
-                FaceDownDetector faceDownDetector) {
+                FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
             return mNotifierMock;
         }
 
@@ -298,6 +298,7 @@
                         BatteryStats.SERVICE_NAME)),
                 mInjector.createSuspendBlocker(mService, "testBlocker"),
                 null,
+                null,
                 null);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 5eabc1b..3d64d4b 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -215,7 +215,7 @@
             @Override
             Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
                     SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
-                    FaceDownDetector faceDownDetector) {
+                    FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
                 return mNotifierMock;
             }
 
diff --git a/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java b/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
index 11554c7..8c4b4ad 100644
--- a/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
@@ -82,6 +82,7 @@
         MockitoAnnotations.initMocks(this);
         when(mUwbInjector.getVendorService()).thenReturn(mVendorService);
         when(mUwbInjector.checkUwbRangingPermissionForDataDelivery(any(), any())).thenReturn(true);
+        when(mUwbInjector.isPersistedUwbStateEnabled()).thenReturn(true);
         when(mVendorService.asBinder()).thenReturn(mVendorServiceBinder);
         mUwbServiceImpl = new UwbServiceImpl(mContext, mUwbInjector);
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 71c05b5..ea46eab 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -32,6 +32,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
@@ -72,6 +73,7 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Slog;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
@@ -1182,6 +1184,7 @@
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
 
         mService.buzzBeepBlinkLocked(r);
+        verifyDelayedVibrate(mService.getVibratorHelper().createFallbackVibration(false));
 
         // quiet update should stop making noise
         mService.buzzBeepBlinkLocked(s);
@@ -1564,6 +1567,32 @@
     }
 
     @Test
+    public void testRingtoneInsistentBeep_canUpdate() throws Exception {
+        NotificationChannel ringtoneChannel =
+                new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+        ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"),
+                new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+        ringtoneChannel.enableVibration(true);
+        NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+        mService.addNotification(ringtoneNotification);
+        assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
+        mService.buzzBeepBlinkLocked(ringtoneNotification);
+        verifyBeepLooped();
+        verifyDelayedVibrateLooped();
+        Mockito.reset(mVibrator);
+        Mockito.reset(mRingtonePlayer);
+
+        assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
+        mService.buzzBeepBlinkLocked(ringtoneNotification);
+
+        // beep wasn't reset
+        verifyNeverBeep();
+        verifyNeverVibrate();
+        verify(mRingtonePlayer, never()).stopAsync();
+        verify(mVibrator, never()).cancel();
+    }
+
+    @Test
     public void testCannotInterruptRingtoneInsistentBuzz() {
         NotificationChannel ringtoneChannel =
                 new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f57c416..f660af0 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -516,7 +516,7 @@
 
         when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
 
-        mWorkerHandler = mService.new WorkerHandler(mTestableLooper.getLooper());
+        mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
         mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
                 mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
                 mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
@@ -2703,6 +2703,42 @@
     }
 
     @Test
+    public void testCrossUserSnooze() {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 10);
+        mService.addNotification(r);
+        NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel, 0);
+        mService.addNotification(r2);
+
+        mListener = mock(ManagedServices.ManagedServiceInfo.class);
+        mListener.component = new ComponentName(PKG, PKG);
+        when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+        mService.snoozeNotificationInt(r.getKey(), 1000, null, mListener);
+
+        verify(mWorkerHandler, never()).post(
+                any(NotificationManagerService.SnoozeNotificationRunnable.class));
+    }
+
+    @Test
+    public void testSameUserSnooze() {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 10);
+        mService.addNotification(r);
+        NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel, 0);
+        mService.addNotification(r2);
+
+        mListener = mock(ManagedServices.ManagedServiceInfo.class);
+        mListener.component = new ComponentName(PKG, PKG);
+        when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+        mService.snoozeNotificationInt(r2.getKey(), 1000, null, mListener);
+
+        verify(mWorkerHandler).post(
+                any(NotificationManagerService.SnoozeNotificationRunnable.class));
+    }
+
+    @Test
     public void testSnoozeRunnable_reSnoozeASingleSnoozedNotification() throws Exception {
         final NotificationRecord notification = generateNotificationRecord(
                 mTestNotificationChannel, 1, null, true);
@@ -4793,6 +4829,52 @@
     }
 
     @Test
+    public void testSetNotificationsShownFromListener_protectsCrossUserInformation()
+            throws RemoteException {
+        Notification.Builder nb = new Notification.Builder(
+                mContext, mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "tag" + System.currentTimeMillis(),  UserHandle.PER_USER_RANGE, 0,
+                nb.build(), UserHandle.getUserHandleForUid(mUid + UserHandle.PER_USER_RANGE),
+                null, 0);
+        final NotificationRecord r =
+                new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+        r.setTextChanged(true);
+        mService.addNotification(r);
+
+        // no security exception!
+        mBinderService.setNotificationsShownFromListener(null, new String[] {r.getKey()});
+
+        verify(mAppUsageStats, never()).reportInterruptiveNotification(
+                anyString(), anyString(), anyInt());
+    }
+
+    @Test
+    public void testCancelNotificationsFromListener_protectsCrossUserInformation()
+            throws RemoteException {
+        Notification.Builder nb = new Notification.Builder(
+                mContext, mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "tag" + System.currentTimeMillis(),  UserHandle.PER_USER_RANGE, 0,
+                nb.build(), UserHandle.getUserHandleForUid(mUid + UserHandle.PER_USER_RANGE),
+                null, 0);
+        final NotificationRecord r =
+                new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+        r.setTextChanged(true);
+        mService.addNotification(r);
+
+        // no security exception!
+        mBinderService.cancelNotificationsFromListener(null, new String[] {r.getKey()});
+
+        waitForIdle();
+        assertEquals(1, mService.getNotificationRecordCount());
+    }
+
+    @Test
     public void testMaybeRecordInterruptionLocked_doesNotRecordTwice()
             throws RemoteException {
         final NotificationRecord r = generateNotificationRecord(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index bf0ed71..66d1577 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -91,6 +91,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
 import android.service.notification.ConversationChannelWrapper;
@@ -376,27 +377,19 @@
         when(mPm.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
     }
 
-    private static NotificationChannel createNotificationChannel(String id, String name,
-            int importance) {
-        NotificationChannel channel = new NotificationChannel(id, name, importance);
-        channel.setSound(SOUND_URI, Notification.AUDIO_ATTRIBUTES_DEFAULT);
-        return channel;
-    }
-
     @Test
     public void testWriteXml_onlyBackupsTargetUser() throws Exception {
         // Setup package notifications.
         String package0 = "test.package.user0";
         int uid0 = 1001;
         setUpPackageWithUid(package0, uid0);
-        NotificationChannel channel0 = createNotificationChannel("id0", "name0", IMPORTANCE_HIGH);
+        NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
         assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));
 
         String package10 = "test.package.user10";
         int uid10 = 1001001;
         setUpPackageWithUid(package10, uid10);
-        NotificationChannel channel10 = createNotificationChannel("id10", "name10",
-                IMPORTANCE_HIGH);
+        NotificationChannel channel10 = new NotificationChannel("id10", "name10", IMPORTANCE_HIGH);
         assertTrue(mHelper.createNotificationChannel(package10, uid10, channel10, true, false));
 
         ByteArrayOutputStream baos = writeXmlAndPurge(package10, uid10, true, 10);
@@ -421,7 +414,7 @@
         String package0 = "test.package.user0";
         int uid0 = 1001;
         setUpPackageWithUid(package0, uid0);
-        NotificationChannel channel0 = createNotificationChannel("id0", "name0", IMPORTANCE_HIGH);
+        NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
         assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));
 
         ByteArrayOutputStream baos = writeXmlAndPurge(package0, uid0, true, 0);
@@ -514,8 +507,9 @@
         NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
         NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
         NotificationChannel channel1 =
-                createNotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
-        NotificationChannel channel2 = createNotificationChannel("id2", "name2", IMPORTANCE_LOW);
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+        NotificationChannel channel2 =
+                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
         channel2.setDescription("descriptions for all");
         channel2.setSound(SOUND_URI, mAudioAttributes);
         channel2.enableLights(true);
@@ -524,7 +518,7 @@
         channel2.enableVibration(false);
         channel2.setGroup(ncg.getId());
         channel2.setLightColor(Color.BLUE);
-        NotificationChannel channel3 = createNotificationChannel("id3", "NAM3", IMPORTANCE_HIGH);
+        NotificationChannel channel3 = new NotificationChannel("id3", "NAM3", IMPORTANCE_HIGH);
         channel3.enableVibration(true);
 
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
@@ -631,8 +625,7 @@
     }
 
     @Test
-    public void testRestoreXml_withNonExistentCanonicalizedSoundUri_ignoreChannel()
-            throws Exception {
+    public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
         Thread.sleep(3000);
         doReturn(null)
                 .when(mTestIContentProvider).canonicalize(any(), eq(CANONICAL_SOUND_URI));
@@ -650,7 +643,7 @@
 
         NotificationChannel actualChannel = mHelper.getNotificationChannel(
                 PKG_N_MR1, UID_N_MR1, channel.getId(), false);
-        assertNull(actualChannel);
+        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
     }
 
 
@@ -659,8 +652,7 @@
      * handle its restore properly.
      */
     @Test
-    public void testRestoreXml_withUncanonicalizedNonLocalSoundUri_ignoreChannel()
-            throws Exception {
+    public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
         // Not a local uncanonicalized uri, simulating that it fails to exist locally
         doReturn(null)
                 .when(mTestIContentProvider).canonicalize(any(), eq(SOUND_URI));
@@ -679,7 +671,7 @@
                 backupWithUncanonicalizedSoundUri.getBytes(), true, UserHandle.USER_SYSTEM);
 
         NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, id, false);
-        assertNull(actualChannel);
+        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
     }
 
     @Test
@@ -703,11 +695,11 @@
         NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
         NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
         NotificationChannel channel1 =
-                createNotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
         NotificationChannel channel2 =
-                createNotificationChannel("id2", "name2", IMPORTANCE_HIGH);
+                new NotificationChannel("id2", "name2", IMPORTANCE_HIGH);
         NotificationChannel channel3 =
-                createNotificationChannel("id3", "name3", IMPORTANCE_LOW);
+                new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
         channel3.setGroup(ncg.getId());
 
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
@@ -3062,7 +3054,7 @@
     @Test
     public void testChannelXml_backupDefaultApp() throws Exception {
         NotificationChannel channel1 =
-                createNotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
 
         mHelper.createNotificationChannel(PKG_O, UID_O, channel1, true, false);
 
@@ -3343,7 +3335,7 @@
                 mAppOpsManager, mStatsEventBuilderFactory);
 
         mHelper.createNotificationChannel(
-                PKG_P, UID_P, createNotificationChannel("id", "id", 2), true, false);
+                PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false);
         assertTrue(mHelper.deleteNotificationChannel(PKG_P, UID_P, "id"));
         assertFalse(mHelper.deleteNotificationChannel(PKG_P, UID_P, "id"));
     }
@@ -3354,7 +3346,7 @@
                 mAppOpsManager, mStatsEventBuilderFactory);
 
         mHelper.createNotificationChannel(
-                PKG_P, UID_P, createNotificationChannel("id", "id", 2), true, false);
+                PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false);
         mHelper.deleteNotificationChannel(PKG_P, UID_P, "id");
         NotificationChannel nc1 = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true);
         assertTrue(DateUtils.isToday(nc1.getDeletedTimeMs()));
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
index 222c692..6b36fe8 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -69,7 +69,7 @@
 
     @Before
     public void setUp() {
-        mDetector = new SingleKeyGestureDetector(mContext);
+        mDetector = new SingleKeyGestureDetector();
         initSingleKeyGestureRules();
         mWaitTimeout = ViewConfiguration.getMultiPressTimeout() + 50;
         mLongPressTime = ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout() + 50;
@@ -78,7 +78,7 @@
     }
 
     private void initSingleKeyGestureRules() {
-        mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER,
+        mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(mContext, KEYCODE_POWER,
                 KEY_LONGPRESS | KEY_VERYLONGPRESS) {
             @Override
             int getMaxMultiPressCount() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 293e862a..6f04f17 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -39,6 +39,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -2816,6 +2817,73 @@
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
     }
 
+    @Test
+    public void testImeInsetsFrozenFlag_resetWhenReparented() {
+        final ActivityRecord activity = createActivityWithTask();
+        final WindowState app = createWindow(null, TYPE_APPLICATION, activity, "app");
+        final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow");
+        final Task newTask = new TaskBuilder(mSupervisor).build();
+        makeWindowVisible(app, imeWindow);
+        mDisplayContent.mInputMethodWindow = imeWindow;
+        mDisplayContent.setImeLayeringTarget(app);
+        mDisplayContent.setImeInputTarget(app);
+
+        // Simulate app is closing and expect the last IME is shown and IME insets is frozen.
+        app.mActivityRecord.commitVisibility(false, false);
+        assertTrue(app.mActivityRecord.mLastImeShown);
+        assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+        // Expect IME insets frozen state will reset when the activity is reparent to the new task.
+        activity.setState(RESUMED, "test");
+        activity.reparent(newTask, 0 /* top */, "test");
+        assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+    }
+
+    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @Test
+    public void testImeInsetsFrozenFlag_resetWhenResized() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        makeWindowVisibleAndDrawn(app, mImeWindow);
+        mDisplayContent.setImeLayeringTarget(app);
+        mDisplayContent.setImeInputTarget(app);
+
+        // Simulate app is closing and expect the last IME is shown and IME insets is frozen.
+        app.mActivityRecord.commitVisibility(false, false);
+        assertTrue(app.mActivityRecord.mLastImeShown);
+        assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+        // Expect IME insets frozen state will reset when the activity is reparent to the new task.
+        app.mActivityRecord.onResize();
+        assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+    }
+
+    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @Test
+    public void testImeInsetsFrozenFlag_resetWhenNoImeFocusableInActivity() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        makeWindowVisibleAndDrawn(app, mImeWindow);
+        mDisplayContent.setImeLayeringTarget(app);
+        mDisplayContent.setImeInputTarget(app);
+
+        // Simulate app is closing and expect the last IME is shown and IME insets is frozen.
+        app.mActivityRecord.commitVisibility(false, false);
+        app.mActivityRecord.onWindowsGone();
+
+        assertTrue(app.mActivityRecord.mLastImeShown);
+        assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+        // Expect IME insets frozen state will reset when the activity has no IME focusable window.
+        app.mActivityRecord.forAllWindowsUnchecked(w -> {
+            w.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
+            return true;
+        }, true);
+
+        app.mActivityRecord.commitVisibility(true, false);
+        app.mActivityRecord.onWindowsVisible();
+
+        assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+    }
+
     private void assertHasStartingWindow(ActivityRecord atoken) {
         assertNotNull(atoken.mStartingSurface);
         assertNotNull(atoken.mStartingData);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 0d177c1..19f9b75 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -30,6 +30,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -40,8 +41,10 @@
 
 import android.app.WaitResult;
 import android.content.ComponentName;
+import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.os.ConditionVariable;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
 
@@ -187,6 +190,24 @@
         verify(taskChangeNotifier, never()).notifyActivityDismissingDockedRootTask();
     }
 
+    /** Ensures that the calling package name passed to client complies with package visibility. */
+    @Test
+    public void testFilteredReferred() {
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setLaunchedFromPackage("other.package").setCreateTask(true).build();
+        assertNotNull(activity.launchedFromPackage);
+        try {
+            mSupervisor.realStartActivityLocked(activity, activity.app, false /* andResume */,
+                    false /* checkConfig */);
+        } catch (RemoteException ignored) {
+        }
+        verify(activity).getFilteredReferrer(eq(activity.launchedFromPackage));
+
+        activity.deliverNewIntentLocked(ActivityBuilder.DEFAULT_FAKE_UID,
+                new Intent(), null /* intentGrants */, "other.package2");
+        verify(activity).getFilteredReferrer(eq("other.package2"));
+    }
+
     /**
      * Ensures that notify focus task changes.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
index f2418c6..a8ede13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
@@ -82,7 +82,7 @@
             mWm.mWindowContextListenerController.registerWindowContainerListener(clientToken,
                     dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG,
                     null /* options */);
-            return true;
+            return dc.getImeContainer().getConfiguration();
         }).when(wms).attachWindowContextToDisplayArea(any(), eq(TYPE_INPUT_METHOD_DIALOG),
                 anyInt(), any());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index a8e1753..8e7ba4bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -258,6 +258,7 @@
         final ActivityManagerInternal amInternal = mAmService.mInternal;
         spyOn(amInternal);
         doNothing().when(amInternal).trimApplications();
+        doNothing().when(amInternal).scheduleAppGcs();
         doNothing().when(amInternal).updateCpuStats();
         doNothing().when(amInternal).updateOomAdj();
         doNothing().when(amInternal).updateBatteryStats(any(), anyInt(), anyInt(), anyBoolean());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index d6a8401..ab496cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -797,6 +797,9 @@
         public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
         }
         @Override
+        public void onImeDrawnOnTask(int taskId) throws RemoteException {
+        }
+        @Override
         public void onAppSplashScreenViewRemoved(int taskId) {
         }
     };
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 92b670e..d88ac25 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -891,6 +891,40 @@
         assertTrue(mAppWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
     }
 
+    @Test
+    public void testAdjustImeInsetsVisibilityWhenSwitchingApps() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
+        final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow");
+        spyOn(imeWindow);
+        doReturn(true).when(imeWindow).isVisible();
+        mDisplayContent.mInputMethodWindow = imeWindow;
+
+        final InsetsStateController controller = mDisplayContent.getInsetsStateController();
+        controller.getImeSourceProvider().setWindow(imeWindow, null, null);
+
+        // Simulate app requests IME with updating all windows Insets State when IME is above app.
+        mDisplayContent.setImeLayeringTarget(app);
+        mDisplayContent.setImeInputTarget(app);
+        assertTrue(mDisplayContent.shouldImeAttachedToApp());
+        controller.getImeSourceProvider().scheduleShowImePostLayout(app);
+        controller.getImeSourceProvider().getSource().setVisible(true);
+        controller.updateAboveInsetsState(imeWindow, false);
+
+        // Expect all app windows behind IME can receive IME insets visible.
+        assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
+        assertTrue(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+
+        // Simulate app plays closing transition to app2.
+        app.mActivityRecord.commitVisibility(false, false);
+        assertTrue(app.mActivityRecord.mLastImeShown);
+        assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+        // Verify the IME insets is visible on app, but not for app2 during app task switching.
+        assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
+        assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+    }
+
     @UseTestDisplay(addWindows = { W_ACTIVITY })
     @Test
     public void testUpdateImeControlTargetWhenLeavingMultiWindow() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 5880899..611b3f5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -801,6 +801,7 @@
         private int mConfigChanges;
         private int mLaunchedFromPid;
         private int mLaunchedFromUid;
+        private String mLaunchedFromPackage;
         private WindowProcessController mWpc;
         private Bundle mIntentExtras;
         private boolean mOnTop = false;
@@ -911,6 +912,11 @@
             return this;
         }
 
+        ActivityBuilder setLaunchedFromPackage(String packageName) {
+            mLaunchedFromPackage = packageName;
+            return this;
+        }
+
         ActivityBuilder setUseProcess(WindowProcessController wpc) {
             mWpc = wpc;
             return this;
@@ -1000,6 +1006,7 @@
             final ActivityRecord activity = new ActivityRecord.Builder(mService)
                     .setLaunchedFromPid(mLaunchedFromPid)
                     .setLaunchedFromUid(mLaunchedFromUid)
+                    .setLaunchedFromPackage(mLaunchedFromPackage)
                     .setIntent(intent)
                     .setActivityInfo(aInfo)
                     .setActivityOptions(options)
diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
index 7b6ccd3..0c65cc4 100644
--- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
@@ -45,6 +45,7 @@
 import android.service.usb.UsbUserPermissionsManagerProto;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
+import android.util.EventLog;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.util.TypedXmlPullParser;
@@ -74,6 +75,8 @@
     private static final String TAG = UsbUserPermissionManager.class.getSimpleName();
     private static final boolean DEBUG = false;
 
+    private static final int SNET_EVENT_LOG_ID = 0x534e4554;
+
     @GuardedBy("mLock")
     /** Mapping of USB device name to list of UIDs with permissions for the device
      * Each entry lasts until device is disconnected*/
@@ -689,8 +692,11 @@
         try {
             ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0);
             if (aInfo.uid != uid) {
-                throw new IllegalArgumentException("package " + packageName
+                Slog.w(TAG, "package " + packageName
                         + " does not match caller's uid " + uid);
+                EventLog.writeEvent(SNET_EVENT_LOG_ID, "180104273", -1, "");
+                throw new IllegalArgumentException("package " + packageName
+                        + " not found");
             }
         } catch (PackageManager.NameNotFoundException e) {
             throw new IllegalArgumentException("package " + packageName + " not found");
diff --git a/services/uwb/java/com/android/server/uwb/UwbInjector.java b/services/uwb/java/com/android/server/uwb/UwbInjector.java
index 64f1da1..7445e7f 100644
--- a/services/uwb/java/com/android/server/uwb/UwbInjector.java
+++ b/services/uwb/java/com/android/server/uwb/UwbInjector.java
@@ -21,10 +21,13 @@
 
 import android.annotation.NonNull;
 import android.content.AttributionSource;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.PermissionChecker;
 import android.os.IBinder;
 import android.os.ServiceManager;
+import android.provider.Settings;
+import android.uwb.AdapterState;
 import android.uwb.IUwbAdapter;
 
 
@@ -80,4 +83,16 @@
                 mContext, UWB_RANGING, -1, attributionSource, message);
         return permissionCheckResult == PERMISSION_GRANTED;
     }
+
+    /** Returns true if UWB state saved in Settings is enabled. */
+    public boolean isPersistedUwbStateEnabled() {
+        final ContentResolver cr = mContext.getContentResolver();
+        try {
+            return Settings.Global.getInt(cr, Settings.Global.UWB_ENABLED)
+                    == AdapterState.STATE_ENABLED_ACTIVE;
+        } catch (Settings.SettingNotFoundException e) {
+            Settings.Global.putInt(cr, Settings.Global.UWB_ENABLED, AdapterState.STATE_DISABLED);
+            return false;
+        }
+    }
 }
diff --git a/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java b/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
index 4dd26a6..da11a9a 100644
--- a/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
+++ b/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
@@ -18,13 +18,16 @@
 
 import android.annotation.NonNull;
 import android.content.AttributionSource;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
+import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.uwb.AdapterState;
 import android.uwb.IUwbAdapter;
 import android.uwb.IUwbAdapterStateCallbacks;
 import android.uwb.IUwbRangingCallbacks;
@@ -225,13 +228,15 @@
         mVendorUwbAdapter = null;
     }
 
-    private synchronized IUwbAdapter getVendorUwbAdapter() throws IllegalStateException {
+    private synchronized IUwbAdapter getVendorUwbAdapter()
+            throws IllegalStateException, RemoteException {
         if (mVendorUwbAdapter != null) return mVendorUwbAdapter;
         mVendorUwbAdapter = mUwbInjector.getVendorService();
         if (mVendorUwbAdapter == null) {
             throw new IllegalStateException("No vendor service found!");
         }
         Log.i(TAG, "Retrieved vendor service");
+        mVendorUwbAdapter.setEnabled(mUwbInjector.isPersistedUwbStateEnabled());
         linkToVendorServiceDeath();
         return mVendorUwbAdapter;
     }
@@ -320,6 +325,13 @@
 
     @Override
     public synchronized void setEnabled(boolean enabled) throws RemoteException {
+        persistUwbState(enabled);
         getVendorUwbAdapter().setEnabled(enabled);
     }
+
+    private void persistUwbState(boolean enabled) {
+        final ContentResolver cr = mContext.getContentResolver();
+        int state = enabled ? AdapterState.STATE_ENABLED_ACTIVE : AdapterState.STATE_DISABLED;
+        Settings.Global.putInt(cr, Settings.Global.UWB_ENABLED, state);
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index a9aeb98..4dc83ae 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -269,13 +269,11 @@
         Slog.v(TAG, "cancelLocked");
         clearDebugHotwordLoggingTimeoutLocked();
         mDebugHotwordLogging = false;
-        if (mRemoteHotwordDetectionService.isBound()) {
-            mRemoteHotwordDetectionService.unbind();
-            LocalServices.getService(PermissionManagerServiceInternal.class)
-                    .setHotwordDetectionServiceProvider(null);
-            mIdentity = null;
-            updateServiceUidForAudioPolicy(Process.INVALID_UID);
-        }
+        mRemoteHotwordDetectionService.unbind();
+        LocalServices.getService(PermissionManagerServiceInternal.class)
+                .setHotwordDetectionServiceProvider(null);
+        mIdentity = null;
+        updateServiceUidForAudioPolicy(Process.INVALID_UID);
         mCancellationTaskFuture.cancel(/* may interrupt */ true);
         if (mAudioFlinger != null) {
             mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 02bd001..bc0a146 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -23,6 +23,8 @@
 import android.os.Bundle;
 import android.util.ArrayMap;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -115,6 +117,7 @@
     public static final int SDK_VERSION_R = 30;
 
     // A Map allows us to track each Call by its Telecom-specified call ID
+    @GuardedBy("mLock")
     private final Map<String, Call> mCallByTelecomCallId = new ArrayMap<>();
 
     // A List allows us to keep the Calls in a stable iteration order so that casually developed
@@ -154,7 +157,7 @@
             return;
         }
 
-        Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+        Call call = getCallById(parcelableCall.getId());
         if (call == null) {
             call = new Call(this, parcelableCall.getId(), mInCallAdapter,
                     parcelableCall.getState(), mCallingPackage, mTargetSdkVersion);
@@ -191,14 +194,14 @@
         if (mTargetSdkVersion < SDK_VERSION_R
                 && parcelableCall.getState() == Call.STATE_AUDIO_PROCESSING) {
             Log.i(this, "removing audio processing call during update for sdk compatibility");
-            Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+            Call call = getCallById(parcelableCall.getId());
             if (call != null) {
                 internalRemoveCall(call);
             }
             return;
         }
 
-        Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+        Call call = getCallById(parcelableCall.getId());
         if (call != null) {
             checkCallTree(parcelableCall);
             call.internalUpdate(parcelableCall, mCallByTelecomCallId);
@@ -215,8 +218,14 @@
         }
     }
 
+    Call getCallById(String callId) {
+        synchronized (mLock) {
+            return mCallByTelecomCallId.get(callId);
+        }
+    }
+
     final void internalSetPostDialWait(String telecomId, String remaining) {
-        Call call = mCallByTelecomCallId.get(telecomId);
+        Call call = getCallById(telecomId);
         if (call != null) {
             call.internalSetPostDialWait(remaining);
         }
@@ -230,7 +239,7 @@
     }
 
     final Call internalGetCallByTelecomId(String telecomId) {
-        return mCallByTelecomCallId.get(telecomId);
+        return getCallById(telecomId);
     }
 
     final void internalBringToForeground(boolean showDialpad) {
@@ -249,35 +258,35 @@
     }
 
     final void internalOnConnectionEvent(String telecomId, String event, Bundle extras) {
-        Call call = mCallByTelecomCallId.get(telecomId);
+        Call call = getCallById(telecomId);
         if (call != null) {
             call.internalOnConnectionEvent(event, extras);
         }
     }
 
     final void internalOnRttUpgradeRequest(String callId, int requestId) {
-        Call call = mCallByTelecomCallId.get(callId);
+        Call call = getCallById(callId);
         if (call != null) {
             call.internalOnRttUpgradeRequest(requestId);
         }
     }
 
     final void internalOnRttInitiationFailure(String callId, int reason) {
-        Call call = mCallByTelecomCallId.get(callId);
+        Call call = getCallById(callId);
         if (call != null) {
             call.internalOnRttInitiationFailure(reason);
         }
     }
 
     final void internalOnHandoverFailed(String callId, int error) {
-        Call call = mCallByTelecomCallId.get(callId);
+        Call call = getCallById(callId);
         if (call != null) {
             call.internalOnHandoverFailed(error);
         }
     }
 
     final void internalOnHandoverComplete(String callId) {
-        Call call = mCallByTelecomCallId.get(callId);
+        Call call = getCallById(callId);
         if (call != null) {
             call.internalOnHandoverComplete();
         }
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 4df8a4b..f248fd5 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -8,10 +8,10 @@
 tgunn@google.com
 jminjie@google.com
 shuoq@google.com
-nazaninb@google.com
 sarahchin@google.com
 xiaotonj@google.com
 huiwang@google.com
 jayachandranc@google.com
 chinmayd@google.com
 amruthr@google.com
+sasindran@google.com
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 8e910c9..277ce15 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5161,6 +5161,16 @@
             "display_no_data_notification_on_permanent_failure_bool";
 
     /**
+     * Boolean indicating if the VoNR setting is visible in the Call Settings menu.
+     * If true, the VoNR setting menu will be visible. If false, the menu will be gone.
+     *
+     * Disabled by default.
+     *
+     * @hide
+     */
+    public static final String KEY_VONR_SETTING_VISIBILITY_BOOL = "vonr_setting_visibility_bool";
+
+    /**
      * Determine whether unthrottle data retry when tracking area code (TAC/LAC) from cell changes
      *
      * @hide
@@ -5774,6 +5784,7 @@
         sDefaults.putString(KEY_CARRIER_PROVISIONING_APP_STRING, "");
         sDefaults.putBoolean(KEY_DISPLAY_NO_DATA_NOTIFICATION_ON_PERMANENT_FAILURE_BOOL, false);
         sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
+        sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, false);
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 255a612..d5be4f3 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3483,7 +3483,8 @@
      */
     private int getLogicalSlotIndex(int physicalSlotIndex) {
         UiccSlotInfo[] slotInfos = getUiccSlotsInfo();
-        if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length) {
+        if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length
+                && slotInfos[physicalSlotIndex] != null) {
             return slotInfos[physicalSlotIndex].getLogicalSlotIdx();
         }
 
@@ -12143,6 +12144,100 @@
     }
 
     /**
+     * No error. Operation succeeded.
+     * @hide
+     */
+    public static final int ENABLE_VONR_SUCCESS = 0;
+
+    /**
+     * Radio is not available.
+     * @hide
+     */
+    public static final int ENABLE_VONR_RADIO_NOT_AVAILABLE = 2;
+
+    /**
+     * Internal Radio error.
+     * @hide
+     */
+    public static final int ENABLE_VONR_RADIO_ERROR = 3;
+
+    /**
+     * Voice over NR enable/disable request is received when system is in invalid state.
+     * @hide
+     */
+    public static final int ENABLE_VONR_RADIO_INVALID_STATE = 4;
+
+    /**
+     * Voice over NR enable/disable request is not supported.
+     * @hide
+     */
+    public static final int ENABLE_VONR_REQUEST_NOT_SUPPORTED = 5;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"EnableVoNrResult"}, value = {
+            ENABLE_VONR_SUCCESS,
+            ENABLE_VONR_RADIO_NOT_AVAILABLE,
+            ENABLE_VONR_RADIO_ERROR,
+            ENABLE_VONR_RADIO_INVALID_STATE,
+            ENABLE_VONR_REQUEST_NOT_SUPPORTED})
+    public @interface EnableVoNrResult {}
+
+    /**
+     * Enable or disable Voice over NR (VoNR)
+     *
+     * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+     * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
+     *
+     * @param enabled  enable or disable VoNR.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public @EnableVoNrResult int setVoNrEnabled(boolean enabled) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.setVoNrEnabled(
+                        getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enabled);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#setVoNrEnabled", e);
+        }
+
+        return ENABLE_VONR_RADIO_INVALID_STATE;
+    }
+
+    /**
+     * Is Voice over NR (VoNR) enabled.
+     * @return true if Voice over NR (VoNR) is enabled else false. Enabled state does not mean
+     *  voice call over NR is active or voice ove NR is available. It means the device is allowed to
+     *  register IMS over NR.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isVoNrEnabled() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.isVoNrEnabled(getSubId());
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "isVoNrEnabled RemoteException", ex);
+            ex.rethrowFromSystemServer();
+        }
+        return false;
+    }
+
+    /**
      * Carrier action to start or stop reporting default network available events.
      *
      * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
@@ -14207,7 +14302,8 @@
             if (callForwardingInfo.getNumber() == null) {
                 throw new IllegalArgumentException("callForwarding number is null");
             }
-            if (callForwardingInfo.getTimeoutSeconds() <= 0) {
+            if (callForwardingReason == CallForwardingInfo.REASON_NO_REPLY
+                        && callForwardingInfo.getTimeoutSeconds() <= 0) {
                 throw new IllegalArgumentException("callForwarding timeout isn't positive");
             }
         }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 63732b5..c47e776 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2229,6 +2229,20 @@
     List<String> getEquivalentHomePlmns(int subId, String callingPackage, String callingFeatureId);
 
     /**
+     * Enable or disable Voice over NR (VoNR)
+     * @param subId the subscription ID that this action applies to.
+     * @param enabled enable or disable VoNR.
+     * @return operation result.
+     */
+    int setVoNrEnabled(int subId, boolean enabled);
+
+    /**
+     * Is voice over NR enabled
+     * @return true if VoNR is enabled else false
+     */
+    boolean isVoNrEnabled(int subId);
+
+    /**
      * Enable/Disable E-UTRA-NR Dual Connectivity
      * @return operation result. See TelephonyManager.EnableNrDualConnectivityResult for
      * details
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index fe8e671..fadc23b 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -528,6 +528,8 @@
     int RIL_REQUEST_SET_ALLOWED_NETWORK_TYPES_BITMAP = 222;
     int RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP = 223;
     int RIL_REQUEST_GET_SLICING_CONFIG = 224;
+    int RIL_REQUEST_ENABLE_VONR = 225;
+    int RIL_REQUEST_IS_VONR_ENABLED = 225;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;