Merge "Add java exports for ImmutabilityAnnotationProcessorUnitTests"
diff --git a/core/api/current.txt b/core/api/current.txt
index b063746..815ccfe 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -52022,6 +52022,7 @@
     field public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 512; // 0x200
     field public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 256; // 0x100
     field public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 128; // 0x80
+    field public static final int CONTENT_CHANGE_TYPE_ENABLED = 4096; // 0x1000
     field public static final int CONTENT_CHANGE_TYPE_ERROR = 2048; // 0x800
     field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10
     field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 134b71a4..87960d7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11982,9 +11982,29 @@
     method public int getStart();
   }
 
+  public final class HotwordAudioStream implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.media.AudioFormat getAudioFormat();
+    method @NonNull public android.os.ParcelFileDescriptor getAudioStream();
+    method @NonNull public android.os.PersistableBundle getMetadata();
+    method @Nullable public android.media.AudioTimestamp getTimestamp();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordAudioStream> CREATOR;
+  }
+
+  public static final class HotwordAudioStream.Builder {
+    ctor public HotwordAudioStream.Builder(@NonNull android.media.AudioFormat, @NonNull android.os.ParcelFileDescriptor);
+    method @NonNull public android.service.voice.HotwordAudioStream build();
+    method @NonNull public android.service.voice.HotwordAudioStream.Builder setAudioFormat(@NonNull android.media.AudioFormat);
+    method @NonNull public android.service.voice.HotwordAudioStream.Builder setAudioStream(@NonNull android.os.ParcelFileDescriptor);
+    method @NonNull public android.service.voice.HotwordAudioStream.Builder setMetadata(@NonNull android.os.PersistableBundle);
+    method @NonNull public android.service.voice.HotwordAudioStream.Builder setTimestamp(@NonNull android.media.AudioTimestamp);
+  }
+
   public final class HotwordDetectedResult implements android.os.Parcelable {
     method public int describeContents();
     method public int getAudioChannel();
+    method @NonNull public java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams();
     method public int getConfidenceLevel();
     method @NonNull public android.os.PersistableBundle getExtras();
     method public int getHotwordDurationMillis();
@@ -12014,6 +12034,7 @@
     ctor public HotwordDetectedResult.Builder();
     method @NonNull public android.service.voice.HotwordDetectedResult build();
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setAudioChannel(int);
+    method @NonNull public android.service.voice.HotwordDetectedResult.Builder setAudioStreams(@NonNull java.util.List<android.service.voice.HotwordAudioStream>);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setConfidenceLevel(int);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setExtras(@NonNull android.os.PersistableBundle);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setHotwordDetectionPersonalized(boolean);
diff --git a/core/java/android/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java
index 79d7b21..3c66a15 100644
--- a/core/java/android/app/smartspace/SmartspaceTarget.java
+++ b/core/java/android/app/smartspace/SmartspaceTarget.java
@@ -245,6 +245,10 @@
     public static final int UI_TEMPLATE_COMBINED_CARDS = 6;
     // Sub-card template whose data is represented by {@link SubCardTemplateData}
     public static final int UI_TEMPLATE_SUB_CARD = 7;
+    // Reserved: 8
+    // Template type used by non-UI template features for sending logging information in the
+    // base template data. This should not be used for UI template features.
+    // public static final int UI_TEMPLATE_LOGGING_ONLY = 8;
 
     /**
      * The types of the Smartspace ui templates.
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index 6956cd4..295171c 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -29,16 +29,18 @@
      *
      * @param doze If true, starts the doze dream component if one has been configured,
      * otherwise starts the user-specified dream.
+     * @param reason The reason to start dreaming, which is logged to help debugging.
      */
-    public abstract void startDream(boolean doze);
+    public abstract void startDream(boolean doze, String reason);
 
     /**
      * Called by the power manager to stop a dream.
      *
      * @param immediate If true, ends the dream summarily, otherwise gives it some time
      * to perform a proper exit transition.
+     * @param reason The reason to stop dreaming, which is logged to help debugging.
      */
-    public abstract void stopDream(boolean immediate);
+    public abstract void stopDream(boolean immediate, String reason);
 
     /**
      * Called by the power manager to determine whether a dream is running.
diff --git a/core/java/android/service/voice/HotwordAudioStream.aidl b/core/java/android/service/voice/HotwordAudioStream.aidl
new file mode 100644
index 0000000..9550c83
--- /dev/null
+++ b/core/java/android/service/voice/HotwordAudioStream.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.voice;
+
+parcelable HotwordAudioStream;
diff --git a/core/java/android/service/voice/HotwordAudioStream.java b/core/java/android/service/voice/HotwordAudioStream.java
new file mode 100644
index 0000000..18375ad
--- /dev/null
+++ b/core/java/android/service/voice/HotwordAudioStream.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.voice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.AudioTimestamp;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+
+/**
+ * Represents an audio stream supporting the hotword detection.
+ *
+ * @hide
+ */
+@DataClass(
+        genConstructor = false,
+        genBuilder = true,
+        genEqualsHashCode = true,
+        genParcelable = true,
+        genToString = true
+)
+@SystemApi
+public final class HotwordAudioStream implements Parcelable {
+
+    /**
+     * The {@link AudioFormat} of the audio stream.
+     */
+    @NonNull
+    private final AudioFormat mAudioFormat;
+
+    /**
+     * This stream starts with the audio bytes used for hotword detection, but continues streaming
+     * the audio until the stream is shutdown by the {@link HotwordDetectionService}.
+     */
+    @NonNull
+    private final ParcelFileDescriptor mAudioStream;
+
+    /**
+     * The timestamp when the {@link #getAudioStream()} was captured by the Audio platform.
+     *
+     * <p>
+     * The {@link HotwordDetectionService} egressing the audio is the owner of the underlying
+     * AudioRecord. The {@link HotwordDetectionService} is expected to optionally populate this
+     * field by {@link AudioRecord#getTimestamp}.
+     * </p>
+     *
+     * <p>
+     * This timestamp can be used in conjunction with the
+     * {@link HotwordDetectedResult#getHotwordOffsetMillis()} and
+     * {@link HotwordDetectedResult#getHotwordDurationMillis()} to translate these durations to
+     * timestamps.
+     * </p>
+     */
+    @Nullable
+    private final AudioTimestamp mTimestamp;
+    private static AudioTimestamp defaultTimestamp() {
+        return null;
+    }
+
+    /**
+     * The metadata associated with the audio stream.
+     */
+    @NonNull
+    private final PersistableBundle mMetadata;
+    private static PersistableBundle defaultMetadata() {
+        return new PersistableBundle();
+    }
+
+    private String timestampToString() {
+        if (mTimestamp == null) {
+            return "";
+        }
+        return "TimeStamp:"
+                + " framePos=" + mTimestamp.framePosition
+                + " nanoTime=" + mTimestamp.nanoTime;
+    }
+
+    private void parcelTimestamp(Parcel dest, int flags) {
+        if (mTimestamp != null) {
+            // mTimestamp is not null, we write it to the parcel, set true.
+            dest.writeBoolean(true);
+            dest.writeLong(mTimestamp.framePosition);
+            dest.writeLong(mTimestamp.nanoTime);
+        } else {
+            // mTimestamp is null, we don't write any value out, set false.
+            dest.writeBoolean(false);
+        }
+    }
+
+    @Nullable
+    private static AudioTimestamp unparcelTimestamp(Parcel in) {
+        // If it is true, it means we wrote the value to the parcel before, parse it.
+        // Otherwise, return null.
+        if (in.readBoolean()) {
+            final AudioTimestamp timeStamp = new AudioTimestamp();
+            timeStamp.framePosition = in.readLong();
+            timeStamp.nanoTime = in.readLong();
+            return timeStamp;
+        } else {
+            return null;
+        }
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/voice/HotwordAudioStream.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    /* package-private */ HotwordAudioStream(
+            @NonNull AudioFormat audioFormat,
+            @NonNull ParcelFileDescriptor audioStream,
+            @Nullable AudioTimestamp timestamp,
+            @NonNull PersistableBundle metadata) {
+        this.mAudioFormat = audioFormat;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAudioFormat);
+        this.mAudioStream = audioStream;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAudioStream);
+        this.mTimestamp = timestamp;
+        this.mMetadata = metadata;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mMetadata);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The {@link AudioFormat} of the audio stream.
+     */
+    @DataClass.Generated.Member
+    public @NonNull AudioFormat getAudioFormat() {
+        return mAudioFormat;
+    }
+
+    /**
+     * This stream starts with the audio bytes used for hotword detection, but continues streaming
+     * the audio until the stream is shutdown by the {@link HotwordDetectionService}.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ParcelFileDescriptor getAudioStream() {
+        return mAudioStream;
+    }
+
+    /**
+     * The timestamp when the {@link #getAudioStream()} was captured by the Audio platform.
+     *
+     * <p>
+     * The {@link HotwordDetectionService} egressing the audio is the owner of the underlying
+     * AudioRecord. The {@link HotwordDetectionService} is expected to optionally populate this
+     * field by {@link AudioRecord#getTimestamp}.
+     * </p>
+     *
+     * <p>
+     * This timestamp can be used in conjunction with the
+     * {@link HotwordDetectedResult#getHotwordOffsetMillis()} and
+     * {@link HotwordDetectedResult#getHotwordDurationMillis()} to translate these durations to
+     * timestamps.
+     * </p>
+     */
+    @DataClass.Generated.Member
+    public @Nullable AudioTimestamp getTimestamp() {
+        return mTimestamp;
+    }
+
+    /**
+     * The metadata associated with the audio stream.
+     */
+    @DataClass.Generated.Member
+    public @NonNull PersistableBundle getMetadata() {
+        return mMetadata;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "HotwordAudioStream { " +
+                "audioFormat = " + mAudioFormat + ", " +
+                "audioStream = " + mAudioStream + ", " +
+                "timestamp = " + timestampToString() + ", " +
+                "metadata = " + mMetadata +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(HotwordAudioStream other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        HotwordAudioStream that = (HotwordAudioStream) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Objects.equals(mAudioFormat, that.mAudioFormat)
+                && Objects.equals(mAudioStream, that.mAudioStream)
+                && Objects.equals(mTimestamp, that.mTimestamp)
+                && Objects.equals(mMetadata, that.mMetadata);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Objects.hashCode(mAudioFormat);
+        _hash = 31 * _hash + Objects.hashCode(mAudioStream);
+        _hash = 31 * _hash + Objects.hashCode(mTimestamp);
+        _hash = 31 * _hash + Objects.hashCode(mMetadata);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mTimestamp != null) flg |= 0x4;
+        dest.writeByte(flg);
+        dest.writeTypedObject(mAudioFormat, flags);
+        dest.writeTypedObject(mAudioStream, flags);
+        parcelTimestamp(dest, flags);
+        dest.writeTypedObject(mMetadata, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ HotwordAudioStream(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        AudioFormat audioFormat = (AudioFormat) in.readTypedObject(AudioFormat.CREATOR);
+        ParcelFileDescriptor audioStream = (ParcelFileDescriptor) in.readTypedObject(ParcelFileDescriptor.CREATOR);
+        AudioTimestamp timestamp = unparcelTimestamp(in);
+        PersistableBundle metadata = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
+
+        this.mAudioFormat = audioFormat;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAudioFormat);
+        this.mAudioStream = audioStream;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAudioStream);
+        this.mTimestamp = timestamp;
+        this.mMetadata = metadata;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mMetadata);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<HotwordAudioStream> CREATOR
+            = new Parcelable.Creator<HotwordAudioStream>() {
+        @Override
+        public HotwordAudioStream[] newArray(int size) {
+            return new HotwordAudioStream[size];
+        }
+
+        @Override
+        public HotwordAudioStream createFromParcel(@NonNull Parcel in) {
+            return new HotwordAudioStream(in);
+        }
+    };
+
+    /**
+     * A builder for {@link HotwordAudioStream}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder {
+
+        private @NonNull AudioFormat mAudioFormat;
+        private @NonNull ParcelFileDescriptor mAudioStream;
+        private @Nullable AudioTimestamp mTimestamp;
+        private @NonNull PersistableBundle mMetadata;
+
+        private long mBuilderFieldsSet = 0L;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param audioFormat
+         *   The {@link AudioFormat} of the audio stream.
+         * @param audioStream
+         *   This stream starts with the audio bytes used for hotword detection, but continues streaming
+         *   the audio until the stream is shutdown by the {@link HotwordDetectionService}.
+         */
+        public Builder(
+                @NonNull AudioFormat audioFormat,
+                @NonNull ParcelFileDescriptor audioStream) {
+            mAudioFormat = audioFormat;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mAudioFormat);
+            mAudioStream = audioStream;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mAudioStream);
+        }
+
+        /**
+         * The {@link AudioFormat} of the audio stream.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setAudioFormat(@NonNull AudioFormat value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mAudioFormat = value;
+            return this;
+        }
+
+        /**
+         * This stream starts with the audio bytes used for hotword detection, but continues streaming
+         * the audio until the stream is shutdown by the {@link HotwordDetectionService}.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setAudioStream(@NonNull ParcelFileDescriptor value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mAudioStream = value;
+            return this;
+        }
+
+        /**
+         * The timestamp when the {@link #getAudioStream()} was captured by the Audio platform.
+         *
+         * <p>
+         * The {@link HotwordDetectionService} egressing the audio is the owner of the underlying
+         * AudioRecord. The {@link HotwordDetectionService} is expected to optionally populate this
+         * field by {@link AudioRecord#getTimestamp}.
+         * </p>
+         *
+         * <p>
+         * This timestamp can be used in conjunction with the
+         * {@link HotwordDetectedResult#getHotwordOffsetMillis()} and
+         * {@link HotwordDetectedResult#getHotwordDurationMillis()} to translate these durations to
+         * timestamps.
+         * </p>
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setTimestamp(@NonNull AudioTimestamp value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mTimestamp = value;
+            return this;
+        }
+
+        /**
+         * The metadata associated with the audio stream.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setMetadata(@NonNull PersistableBundle value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8;
+            mMetadata = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull HotwordAudioStream build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x10; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mTimestamp = defaultTimestamp();
+            }
+            if ((mBuilderFieldsSet & 0x8) == 0) {
+                mMetadata = defaultMetadata();
+            }
+            HotwordAudioStream o = new HotwordAudioStream(
+                    mAudioFormat,
+                    mAudioStream,
+                    mTimestamp,
+                    mMetadata);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x10) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1665463434564L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/service/voice/HotwordAudioStream.java",
+            inputSignatures = "private final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStream\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static  android.media.AudioTimestamp defaultTimestamp()\nprivate static  android.os.PersistableBundle defaultMetadata()\nprivate  java.lang.String timestampToString()\nprivate  void parcelTimestamp(android.os.Parcel,int)\nprivate static @android.annotation.Nullable android.media.AudioTimestamp unparcelTimestamp(android.os.Parcel)\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index ab71459..6255d00 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -31,6 +31,8 @@
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Preconditions;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -196,6 +198,15 @@
     }
 
     /**
+     * The list of the audio streams containing audio bytes that were used for hotword detection.
+     */
+    @NonNull
+    private final List<HotwordAudioStream> mAudioStreams;
+    private static List<HotwordAudioStream> defaultAudioStreams() {
+        return new ArrayList<>();
+    }
+
+    /**
      * App-specific extras to support trigger.
      *
      * <p>The size of this bundle will be limited to {@link #getMaxBundleSize}. Results will larger
@@ -353,6 +364,11 @@
         }
     }
 
+    @DataClass.Suppress("addAudioStreams")
+    abstract static class BaseBuilder {
+
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -436,6 +452,7 @@
             int score,
             int personalizedScore,
             int hotwordPhraseId,
+            @NonNull List<HotwordAudioStream> audioStreams,
             @NonNull PersistableBundle extras) {
         this.mConfidenceLevel = confidenceLevel;
         com.android.internal.util.AnnotationValidations.validate(
@@ -448,6 +465,9 @@
         this.mScore = score;
         this.mPersonalizedScore = personalizedScore;
         this.mHotwordPhraseId = hotwordPhraseId;
+        this.mAudioStreams = audioStreams;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAudioStreams);
         this.mExtras = extras;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mExtras);
@@ -535,6 +555,14 @@
     }
 
     /**
+     * The list of the audio streams containing audio bytes that were used for hotword detection.
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<HotwordAudioStream> getAudioStreams() {
+        return mAudioStreams;
+    }
+
+    /**
      * App-specific extras to support trigger.
      *
      * <p>The size of this bundle will be limited to {@link #getMaxBundleSize}. Results will larger
@@ -578,6 +606,7 @@
                 "score = " + mScore + ", " +
                 "personalizedScore = " + mPersonalizedScore + ", " +
                 "hotwordPhraseId = " + mHotwordPhraseId + ", " +
+                "audioStreams = " + mAudioStreams + ", " +
                 "extras = " + mExtras +
         " }";
     }
@@ -604,6 +633,7 @@
                 && mScore == that.mScore
                 && mPersonalizedScore == that.mPersonalizedScore
                 && mHotwordPhraseId == that.mHotwordPhraseId
+                && Objects.equals(mAudioStreams, that.mAudioStreams)
                 && Objects.equals(mExtras, that.mExtras);
     }
 
@@ -623,6 +653,7 @@
         _hash = 31 * _hash + mScore;
         _hash = 31 * _hash + mPersonalizedScore;
         _hash = 31 * _hash + mHotwordPhraseId;
+        _hash = 31 * _hash + Objects.hashCode(mAudioStreams);
         _hash = 31 * _hash + Objects.hashCode(mExtras);
         return _hash;
     }
@@ -645,6 +676,7 @@
         dest.writeInt(mScore);
         dest.writeInt(mPersonalizedScore);
         dest.writeInt(mHotwordPhraseId);
+        dest.writeParcelableList(mAudioStreams, flags);
         dest.writeTypedObject(mExtras, flags);
     }
 
@@ -669,6 +701,8 @@
         int score = in.readInt();
         int personalizedScore = in.readInt();
         int hotwordPhraseId = in.readInt();
+        List<HotwordAudioStream> audioStreams = new ArrayList<>();
+        in.readParcelableList(audioStreams, HotwordAudioStream.class.getClassLoader());
         PersistableBundle extras = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
 
         this.mConfidenceLevel = confidenceLevel;
@@ -682,6 +716,9 @@
         this.mScore = score;
         this.mPersonalizedScore = personalizedScore;
         this.mHotwordPhraseId = hotwordPhraseId;
+        this.mAudioStreams = audioStreams;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mAudioStreams);
         this.mExtras = extras;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mExtras);
@@ -708,7 +745,7 @@
      */
     @SuppressWarnings("WeakerAccess")
     @DataClass.Generated.Member
-    public static final class Builder {
+    public static final class Builder extends BaseBuilder {
 
         private @HotwordConfidenceLevelValue int mConfidenceLevel;
         private @Nullable MediaSyncEvent mMediaSyncEvent;
@@ -719,6 +756,7 @@
         private int mScore;
         private int mPersonalizedScore;
         private int mHotwordPhraseId;
+        private @NonNull List<HotwordAudioStream> mAudioStreams;
         private @NonNull PersistableBundle mExtras;
 
         private long mBuilderFieldsSet = 0L;
@@ -843,6 +881,17 @@
         }
 
         /**
+         * The list of the audio streams containing audio bytes that were used for hotword detection.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setAudioStreams(@NonNull List<HotwordAudioStream> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x200;
+            mAudioStreams = value;
+            return this;
+        }
+
+        /**
          * App-specific extras to support trigger.
          *
          * <p>The size of this bundle will be limited to {@link #getMaxBundleSize}. Results will larger
@@ -868,7 +917,7 @@
         @DataClass.Generated.Member
         public @NonNull Builder setExtras(@NonNull PersistableBundle value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x200;
+            mBuilderFieldsSet |= 0x400;
             mExtras = value;
             return this;
         }
@@ -876,7 +925,7 @@
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull HotwordDetectedResult build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x400; // Mark builder used
+            mBuilderFieldsSet |= 0x800; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mConfidenceLevel = defaultConfidenceLevel();
@@ -906,6 +955,9 @@
                 mHotwordPhraseId = defaultHotwordPhraseId();
             }
             if ((mBuilderFieldsSet & 0x200) == 0) {
+                mAudioStreams = defaultAudioStreams();
+            }
+            if ((mBuilderFieldsSet & 0x400) == 0) {
                 mExtras = defaultExtras();
             }
             HotwordDetectedResult o = new HotwordDetectedResult(
@@ -918,12 +970,13 @@
                     mScore,
                     mPersonalizedScore,
                     mHotwordPhraseId,
+                    mAudioStreams,
                     mExtras);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x400) != 0) {
+            if ((mBuilderFieldsSet & 0x800) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -931,10 +984,10 @@
     }
 
     @DataClass.Generated(
-            time = 1658357814396L,
+            time = 1664876310951L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
-            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final  java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final  java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index cb8f0af..d1f05ec 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -96,13 +96,6 @@
     /** @hide */
     public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping";
 
-    /** Flag to enable / disable the Simple Cursor accessibility feature in
-     *  Settings.
-     * @hide
-     */
-    public static final String SETTINGS_ACCESSIBILITY_SIMPLE_CURSOR =
-            "settings_accessibility_simple_cursor";
-
     /**
      * Enable new language and keyboard settings UI
      * @hide
@@ -149,7 +142,6 @@
         DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
         DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
         DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
-        DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_SIMPLE_CURSOR, "false");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
         DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index bc665cf..5c899e4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -12538,6 +12538,8 @@
         if (!enabled) {
             cancelPendingInputEvents();
         }
+        notifyViewAccessibilityStateChangedIfNeeded(
+                AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED);
     }
 
     /**
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index f86f51fc..b0cf504 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -709,6 +709,18 @@
      */
     public static final int CONTENT_CHANGE_TYPE_ERROR = 0x0000800;
 
+    /**
+     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * The source node changed its ability to interact returned by
+     * {@link AccessibilityNodeInfo#isEnabled}.
+     * The view changing content's ability to interact should call
+     * {@link AccessibilityNodeInfo#setEnabled} and then send this event.
+     *
+     * @see AccessibilityNodeInfo#isEnabled
+     * @see AccessibilityNodeInfo#setEnabled
+     */
+    public static final int CONTENT_CHANGE_TYPE_ENABLED = 1 << 12;
+
     /** Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is speaking. */
     public static final int SPEECH_STATE_SPEAKING_START = 0x00000001;
 
@@ -836,6 +848,7 @@
                 CONTENT_CHANGE_TYPE_DRAG_CANCELLED,
                 CONTENT_CHANGE_TYPE_CONTENT_INVALID,
                 CONTENT_CHANGE_TYPE_ERROR,
+                CONTENT_CHANGE_TYPE_ENABLED,
             })
     public @interface ContentChangeTypes {}
 
@@ -1105,6 +1118,7 @@
             case CONTENT_CHANGE_TYPE_CONTENT_INVALID:
                 return "CONTENT_CHANGE_TYPE_CONTENT_INVALID";
             case CONTENT_CHANGE_TYPE_ERROR: return "CONTENT_CHANGE_TYPE_ERROR";
+            case CONTENT_CHANGE_TYPE_ENABLED: return "CONTENT_CHANGE_TYPE_ENABLED";
             default: return Integer.toHexString(type);
         }
     }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 307c55c..d0405f0 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -267,6 +267,12 @@
  * they can switch to it, to confirm with the system that they know about it
  * and want to make it available for use.</p>
  * </ul>
+ *
+ * <p>If your app targets Android 11 (API level 30) or higher, the methods in
+ * this class each return a filtered result by the rules of
+ * <a href="/training/basics/intents/package-visibility">package visibility</a>,
+ * except for the currently connected IME. Apps having a query for the
+ * {@link InputMethod#SERVICE_INTERFACE} see all IMEs.</p>
  */
 @SystemService(Context.INPUT_METHOD_SERVICE)
 @RequiresFeature(PackageManager.FEATURE_INPUT_METHODS)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 203ece0..1174b68 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -240,6 +240,16 @@
     }
 
     /**
+     * Clears the listener set in {@link SplitController#setSplitInfoListener}.
+     */
+    @Override
+    public void clearSplitInfoCallback() {
+        synchronized (mLock) {
+            mEmbeddingCallback = null;
+        }
+    }
+
+    /**
      * Called when the transaction is ready so that the organizer can update the TaskFragments based
      * on the changes in transaction.
      */
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index b0b95f9..4978e04 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 419e62d..c2ad1a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -118,6 +118,7 @@
     private boolean mFreezeDividerWindow = false;
     private int mOrientation;
     private int mRotation;
+    private int mDensity;
 
     private final boolean mDimNonImeSide;
 
@@ -290,9 +291,11 @@
         final int rotation = configuration.windowConfiguration.getRotation();
         final Rect rootBounds = configuration.windowConfiguration.getBounds();
         final int orientation = configuration.orientation;
+        final int density = configuration.densityDpi;
 
         if (mOrientation == orientation
                 && mRotation == rotation
+                && mDensity == density
                 && mRootBounds.equals(rootBounds)) {
             return false;
         }
@@ -303,6 +306,7 @@
         mTempRect.set(mRootBounds);
         mRootBounds.set(rootBounds);
         mRotation = rotation;
+        mDensity = density;
         mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
         initDividerPosition(mTempRect);
         updateInvisibleRect();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 695550d..f6d6c03 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.common.split;
 
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -91,6 +92,14 @@
         // Verify updateConfiguration returns true if the root bounds changed.
         config.windowConfiguration.setBounds(new Rect(0, 0, 2160, 1080));
         assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
+
+        // Verify updateConfiguration returns true if the orientation changed.
+        config.orientation = ORIENTATION_LANDSCAPE;
+        assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
+
+        // Verify updateConfiguration returns true if the density changed.
+        config.densityDpi = 123;
+        assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
     }
 
     @Test
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index a5c0924..b763a96 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -169,6 +169,8 @@
                 sk_sp<SkImage> layerImage = mImageSlots[slot].createIfNeeded(
                         hardwareBuffer, dataspace, newContent,
                         mRenderState.getRenderThread().getGrContext());
+                AHardwareBuffer_Desc bufferDesc;
+                AHardwareBuffer_describe(hardwareBuffer, &bufferDesc);
                 // unref to match the ref added by ASurfaceTexture_dequeueBuffer. eglCreateImageKHR
                 // (invoked by createIfNeeded) will add a ref to the AHardwareBuffer.
                 AHardwareBuffer_release(hardwareBuffer);
@@ -189,6 +191,7 @@
                         maxLuminanceNits =
                                 std::max(cta861_3.maxContentLightLevel, maxLuminanceNits);
                     }
+                    mLayer->setBufferFormat(bufferDesc.format);
                     updateLayer(forceFilter, layerImage, outTransform, currentCropRect,
                                 maxLuminanceNits);
                 }
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 47eb5d3..345749b 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -102,6 +102,10 @@
 
     inline float getMaxLuminanceNits() { return mMaxLuminanceNits; }
 
+    void setBufferFormat(uint32_t format) { mBufferFormat = format; }
+
+    uint32_t getBufferFormat() const { return mBufferFormat; }
+
     void draw(SkCanvas* canvas);
 
 protected:
@@ -169,6 +173,8 @@
      */
     float mMaxLuminanceNits = -1;
 
+    uint32_t mBufferFormat = 0;
+
 };  // struct Layer
 
 }  // namespace uirenderer
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 2fba13c..3ba5409 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -107,6 +107,32 @@
     return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
 }
 
+static void adjustCropForYUV(uint32_t format, int bufferWidth, int bufferHeight, SkRect* cropRect) {
+    // Chroma channels of YUV420 images are subsampled we may need to shrink the crop region by
+    // a whole texel on each side. Since skia still adds its own 0.5 inset, we apply an
+    // additional 0.5 inset. See GLConsumer::computeTransformMatrix for details.
+    float shrinkAmount = 0.0f;
+    switch (format) {
+        // Use HAL formats since some AHB formats are only available in vndk
+        case HAL_PIXEL_FORMAT_YCBCR_420_888:
+        case HAL_PIXEL_FORMAT_YV12:
+        case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+            shrinkAmount = 0.5f;
+            break;
+        default:
+            break;
+    }
+
+    // Shrink the crop if it has more than 1-px and differs from the buffer size.
+    if (cropRect->width() > 1 && cropRect->width() < bufferWidth) {
+        cropRect->inset(shrinkAmount, 0);
+    }
+
+    if (cropRect->height() > 1 && cropRect->height() < bufferHeight) {
+        cropRect->inset(0, shrinkAmount);
+    }
+}
+
 // TODO: Context arg probably doesn't belong here – do debug check at callsite instead.
 bool LayerDrawable::DrawLayer(GrRecordingContext* context,
                               SkCanvas* canvas,
@@ -142,6 +168,7 @@
         SkRect skiaSrcRect;
         if (srcRect && !srcRect->isEmpty()) {
             skiaSrcRect = *srcRect;
+            adjustCropForYUV(layer->getBufferFormat(), imageWidth, imageHeight, &skiaSrcRect);
         } else {
             skiaSrcRect = SkRect::MakeIWH(imageWidth, imageHeight);
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
index eff9e74..ee65ef4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
@@ -22,8 +22,11 @@
 public class AccessibilityContentDescriptions {
 
     private AccessibilityContentDescriptions() {}
+
+    public static final int PHONE_SIGNAL_STRENGTH_NONE = R.string.accessibility_no_phone;
+
     public static final int[] PHONE_SIGNAL_STRENGTH = {
-        R.string.accessibility_no_phone,
+        PHONE_SIGNAL_STRENGTH_NONE,
         R.string.accessibility_phone_one_bar,
         R.string.accessibility_phone_two_bars,
         R.string.accessibility_phone_three_bars,
diff --git a/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcon.kt b/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcon.kt
new file mode 100644
index 0000000..3daf8c2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcon.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 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.settingslib
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+
+/**
+ * A specification for the icon displaying the mobile network type -- 4G, 5G, LTE, etc. (aka "RAT
+ * icon" or "data type icon"). This is *not* the signal strength triangle.
+ *
+ * This is intended to eventually replace [SignalIcon.MobileIconGroup]. But for now,
+ * [MobileNetworkTypeIcons] just reads from the existing set of [SignalIcon.MobileIconGroup]
+ * instances to not duplicate data.
+ *
+ * TODO(b/238425913): Remove [SignalIcon.MobileIconGroup] and replace it with this class so that we
+ *   don't need to fill in the superfluous fields from its parent [SignalIcon.IconGroup] class. Then
+ *   this class can become either a sealed class or an enum with parameters.
+ */
+data class MobileNetworkTypeIcon(
+    /** A human-readable name for this network type, used for logging. */
+    val name: String,
+
+    /** The resource ID of the icon drawable to use. */
+    @DrawableRes val iconResId: Int,
+
+    /** The resource ID of the content description to use. */
+    @StringRes val contentDescriptionResId: Int,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcons.kt b/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcons.kt
new file mode 100644
index 0000000..2c5ee89
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcons.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.settingslib
+
+import com.android.settingslib.mobile.TelephonyIcons.ICON_NAME_TO_ICON
+
+/**
+ * A utility class to fetch instances of [MobileNetworkTypeIcon] given a
+ * [SignalIcon.MobileIconGroup].
+ *
+ * Use [getNetworkTypeIcon] to fetch the instances.
+ */
+class MobileNetworkTypeIcons {
+    companion object {
+        /**
+         * A map from a [SignalIcon.MobileIconGroup.name] to an instance of [MobileNetworkTypeIcon],
+         * which is the preferred class going forward.
+         */
+        private val MOBILE_NETWORK_TYPE_ICONS: Map<String, MobileNetworkTypeIcon>
+
+        init {
+            // Build up the mapping from the old implementation to the new one.
+            val tempMap: MutableMap<String, MobileNetworkTypeIcon> = mutableMapOf()
+
+            ICON_NAME_TO_ICON.forEach { (_, mobileIconGroup) ->
+                tempMap[mobileIconGroup.name] = mobileIconGroup.toNetworkTypeIcon()
+            }
+
+            MOBILE_NETWORK_TYPE_ICONS = tempMap
+        }
+
+        /**
+         * A converter function between the old mobile network type icon implementation and the new
+         * one. Given an instance of the old class [mobileIconGroup], outputs an instance of the
+         * new class [MobileNetworkTypeIcon].
+         */
+        @JvmStatic
+        fun getNetworkTypeIcon(
+            mobileIconGroup: SignalIcon.MobileIconGroup
+        ): MobileNetworkTypeIcon {
+            return MOBILE_NETWORK_TYPE_ICONS[mobileIconGroup.name]
+                ?: mobileIconGroup.toNetworkTypeIcon()
+        }
+
+        private fun SignalIcon.MobileIconGroup.toNetworkTypeIcon(): MobileNetworkTypeIcon {
+            return MobileNetworkTypeIcon(
+                name = this.name,
+                iconResId = this.dataType,
+                contentDescriptionResId = this.dataContentDescription
+            )
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java b/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
index 280e407..6aaab3c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
+++ b/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
@@ -15,6 +15,9 @@
  */
 package com.android.settingslib;
 
+import androidx.annotation.DrawableRes;
+import androidx.annotation.StringRes;
+
 /**
  * Icons for SysUI and Settings.
  */
@@ -66,34 +69,31 @@
     }
 
     /**
-     * Holds icons for a given MobileState.
+     * Holds RAT icons for a given MobileState.
      */
     public static class MobileIconGroup extends IconGroup {
-        public final int dataContentDescription; // mContentDescriptionDataType
-        public final int dataType;
+        @StringRes public final int dataContentDescription;
+        @DrawableRes public final int dataType;
 
         public MobileIconGroup(
                 String name,
-                int[][] sbIcons,
-                int[][] qsIcons,
-                int[] contentDesc,
-                int sbNullState,
-                int qsNullState,
-                int sbDiscState,
-                int qsDiscState,
-                int discContentDesc,
                 int dataContentDesc,
                 int dataType
         ) {
             super(name,
-                    sbIcons,
-                    qsIcons,
-                    contentDesc,
-                    sbNullState,
-                    qsNullState,
-                    sbDiscState,
-                    qsDiscState,
-                    discContentDesc);
+                    // The rest of the values are the same for every type of MobileIconGroup, so
+                    // just provide them here.
+                    // TODO(b/238425913): Eventually replace with {@link MobileNetworkTypeIcon} so
+                    //  that we don't have to fill in these superfluous fields.
+                    /* sbIcons= */ null,
+                    /* qsIcons= */ null,
+                    /* contentDesc= */ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+                    /* sbNullState= */ 0,
+                    /* qsNullState= */ 0,
+                    /* sbDiscState= */ 0,
+                    /* qsDiscState= */ 0,
+                    /* discContentDesc= */
+                        AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE);
             this.dataContentDescription = dataContentDesc;
             this.dataType = dataType;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
index 23e0923..094567c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.mobile;
 
-import com.android.settingslib.AccessibilityContentDescriptions;
 import com.android.settingslib.R;
 import com.android.settingslib.SignalIcon.MobileIconGroup;
 
@@ -49,297 +48,129 @@
 
     public static final MobileIconGroup CARRIER_NETWORK_CHANGE = new MobileIconGroup(
             "CARRIER_NETWORK_CHANGE",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.carrier_network_change_mode,
-            0
+            /* dataType= */ 0
     );
 
     public static final MobileIconGroup THREE_G = new MobileIconGroup(
             "3G",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_3g,
             TelephonyIcons.ICON_3G
     );
 
     public static final MobileIconGroup WFC = new MobileIconGroup(
             "WFC",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
-            0,
-            0);
+            /* dataContentDescription= */ 0,
+            /* dataType= */ 0);
 
     public static final MobileIconGroup UNKNOWN = new MobileIconGroup(
             "Unknown",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
-            0,
-            0);
+            /* dataContentDescription= */ 0,
+            /* dataType= */ 0);
 
     public static final MobileIconGroup E = new MobileIconGroup(
             "E",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_edge,
             TelephonyIcons.ICON_E
     );
 
     public static final MobileIconGroup ONE_X = new MobileIconGroup(
             "1X",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_cdma,
             TelephonyIcons.ICON_1X
     );
 
     public static final MobileIconGroup G = new MobileIconGroup(
             "G",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_gprs,
             TelephonyIcons.ICON_G
     );
 
     public static final MobileIconGroup H = new MobileIconGroup(
             "H",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_3_5g,
             TelephonyIcons.ICON_H
     );
 
     public static final MobileIconGroup H_PLUS = new MobileIconGroup(
             "H+",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_3_5g_plus,
             TelephonyIcons.ICON_H_PLUS
     );
 
     public static final MobileIconGroup FOUR_G = new MobileIconGroup(
             "4G",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_4g,
             TelephonyIcons.ICON_4G
     );
 
     public static final MobileIconGroup FOUR_G_PLUS = new MobileIconGroup(
             "4G+",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_4g_plus,
             TelephonyIcons.ICON_4G_PLUS
     );
 
     public static final MobileIconGroup LTE = new MobileIconGroup(
             "LTE",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_lte,
             TelephonyIcons.ICON_LTE
     );
 
     public static final MobileIconGroup LTE_PLUS = new MobileIconGroup(
             "LTE+",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_lte_plus,
             TelephonyIcons.ICON_LTE_PLUS
     );
 
     public static final MobileIconGroup FOUR_G_LTE = new MobileIconGroup(
             "4G LTE",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_4g_lte,
             TelephonyIcons.ICON_4G_LTE
     );
 
     public static final MobileIconGroup FOUR_G_LTE_PLUS = new MobileIconGroup(
             "4G LTE+",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_4g_lte_plus,
             TelephonyIcons.ICON_4G_LTE_PLUS
     );
 
     public static final MobileIconGroup LTE_CA_5G_E = new MobileIconGroup(
             "5Ge",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_5ge_html,
             TelephonyIcons.ICON_5G_E
     );
 
     public static final MobileIconGroup NR_5G = new MobileIconGroup(
             "5G",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_5g,
             TelephonyIcons.ICON_5G
     );
 
     public static final MobileIconGroup NR_5G_PLUS = new MobileIconGroup(
             "5G_PLUS",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_5g_plus,
             TelephonyIcons.ICON_5G_PLUS
     );
 
     public static final MobileIconGroup DATA_DISABLED = new MobileIconGroup(
             "DataDisabled",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.cell_data_off_content_description,
             0
     );
 
     public static final MobileIconGroup NOT_DEFAULT_DATA = new MobileIconGroup(
             "NotDefaultData",
-            null,
-            null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            0,
-            0,
-            0,
-            0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.not_default_data_content_description,
-            0
+            /* dataType= */ 0
     );
 
     public static final MobileIconGroup CARRIER_MERGED_WIFI = new MobileIconGroup(
             "CWF",
-            /* sbIcons= */ null,
-            /* qsIcons= */ null,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
-            /* sbNullState= */ 0,
-            /* qsNullState= */ 0,
-            /* sbDiscState= */ 0,
-            /* qsDiscState= */ 0,
-            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
             R.string.data_connection_carrier_wifi,
             TelephonyIcons.ICON_CWF
     );
 
-    // When adding a new MobileIconGround, check if the dataContentDescription has to be filtered
+    // When adding a new MobileIconGroup, check if the dataContentDescription has to be filtered
     // in QSCarrier#hasValidTypeContentDescription
 
     /** Mapping icon name(lower case) to the icon object. */
@@ -368,14 +199,6 @@
         ICON_NAME_TO_ICON.put("notdefaultdata", NOT_DEFAULT_DATA);
     }
 
-    public static final int[] WIFI_CALL_STRENGTH_ICONS = {
-        R.drawable.ic_wifi_call_strength_0,
-        R.drawable.ic_wifi_call_strength_1,
-        R.drawable.ic_wifi_call_strength_2,
-        R.drawable.ic_wifi_call_strength_3,
-        R.drawable.ic_wifi_call_strength_4
-    };
-
     public static final int[] MOBILE_CALL_STRENGTH_ICONS = {
         R.drawable.ic_mobile_call_strength_0,
         R.drawable.ic_mobile_call_strength_1,
@@ -384,4 +207,3 @@
         R.drawable.ic_mobile_call_strength_4
     };
 }
-
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
new file mode 100644
index 0000000..39977df
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 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.settingslib;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settingslib.mobile.TelephonyIcons;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class MobileNetworkTypeIconsTest {
+
+    @Test
+    public void getNetworkTypeIcon_hPlus_returnsHPlus() {
+        MobileNetworkTypeIcon icon =
+                MobileNetworkTypeIcons.getNetworkTypeIcon(TelephonyIcons.H_PLUS);
+
+        assertThat(icon.getName()).isEqualTo(TelephonyIcons.H_PLUS.name);
+        assertThat(icon.getIconResId()).isEqualTo(TelephonyIcons.ICON_H_PLUS);
+    }
+
+    @Test
+    public void getNetworkTypeIcon_fourG_returnsFourG() {
+        MobileNetworkTypeIcon icon =
+                MobileNetworkTypeIcons.getNetworkTypeIcon(TelephonyIcons.FOUR_G);
+
+        assertThat(icon.getName()).isEqualTo(TelephonyIcons.H_PLUS.name);
+        assertThat(icon.getIconResId()).isEqualTo(TelephonyIcons.ICON_4G);
+    }
+
+    @Test
+    public void getNetworkTypeIcon_unknown_returnsUnknown() {
+        SignalIcon.MobileIconGroup unknownGroup =
+                new SignalIcon.MobileIconGroup("testUnknownNameHere", 45, 6);
+
+        MobileNetworkTypeIcon icon = MobileNetworkTypeIcons.getNetworkTypeIcon(unknownGroup);
+
+        assertThat(icon.getName()).isEqualTo("testUnknownNameHere");
+        assertThat(icon.getIconResId()).isEqualTo(45);
+        assertThat(icon.getContentDescriptionResId()).isEqualTo(6);
+    }
+}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 5e0d935..753e110 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -221,7 +221,7 @@
         "metrics-helper-lib",
         "hamcrest-library",
         "androidx.test.rules",
-        "androidx.test.uiautomator",
+        "androidx.test.uiautomator_uiautomator",
         "mockito-target-extended-minus-junit4",
         "testables",
         "truth-prebuilt",
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 8df8c49..6120863 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -59,7 +59,7 @@
 
     </LinearLayout>
 
-    <ImageView
+    <com.android.systemui.common.ui.view.LaunchableImageView
         android:id="@+id/start_button"
         android:layout_height="@dimen/keyguard_affordance_fixed_height"
         android:layout_width="@dimen/keyguard_affordance_fixed_width"
@@ -71,7 +71,7 @@
         android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
         android:visibility="gone" />
 
-    <ImageView
+    <com.android.systemui.common.ui.view.LaunchableImageView
         android:id="@+id/end_button"
         android:layout_height="@dimen/keyguard_affordance_fixed_height"
         android:layout_width="@dimen/keyguard_affordance_fixed_width"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index ee30972..8086172 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -250,8 +250,9 @@
     public boolean startActivityFromRecents(int taskId, ActivityOptions options) {
         try {
             Bundle optsBundle = options == null ? null : options.toBundle();
-            getService().startActivityFromRecents(taskId, optsBundle);
-            return true;
+            return ActivityManager.isStartResultSuccessful(
+                    getService().startActivityFromRecents(
+                            taskId, optsBundle));
         } catch (Exception e) {
             return false;
         }
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index 74bd9c6..bb3df8f 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.os.Handler
+import com.android.internal.statusbar.IStatusBarService
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlagsDebug.ALL_FLAGS
 import com.android.systemui.util.settings.SettingsUtilModule
@@ -27,6 +28,7 @@
 import javax.inject.Named
 
 @Module(includes = [
+    FeatureFlagsDebugStartableModule::class,
     ServerFlagReaderModule::class,
     SettingsUtilModule::class,
 ])
@@ -46,5 +48,15 @@
         @Provides
         @Named(ALL_FLAGS)
         fun providesAllFlags(): Map<Int, Flag<*>> = Flags.collectFlags()
+
+        @JvmStatic
+        @Provides
+        fun providesRestarter(barService: IStatusBarService): Restarter {
+            return object: Restarter {
+                override fun restart() {
+                    barService.restart()
+                }
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 38b5c9a..0f7e732 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -16,11 +16,29 @@
 
 package com.android.systemui.flags
 
+import com.android.internal.statusbar.IStatusBarService
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 
-@Module(includes = [ServerFlagReaderModule::class])
+@Module(includes = [
+    FeatureFlagsReleaseStartableModule::class,
+    ServerFlagReaderModule::class
+])
 abstract class FlagsModule {
     @Binds
     abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
+
+    @Module
+    companion object {
+        @JvmStatic
+        @Provides
+        fun providesRestarter(barService: IStatusBarService): Restarter {
+            return object: Restarter {
+                override fun restart() {
+                    barService.restart()
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 19ffa30..3e796cd0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -806,7 +806,7 @@
         }
         mContainerState = STATE_GONE;
         if (isAttachedToWindow()) {
-            mWindowManager.removeView(this);
+            mWindowManager.removeViewImmediate(this);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
new file mode 100644
index 0000000..f95a8ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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.common.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.ImageView
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
+
+class LaunchableImageView : ImageView, LaunchableView {
+    private val delegate =
+        LaunchableViewDelegate(
+            this,
+            superSetVisibility = { super.setVisibility(it) },
+            superSetTransitionVisibility = { super.setTransitionVisibility(it) },
+        )
+
+    constructor(context: Context?) : super(context)
+    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+    constructor(
+        context: Context?,
+        attrs: AttributeSet?,
+        defStyleAttr: Int,
+    ) : super(context, attrs, defStyleAttr)
+
+    constructor(
+        context: Context?,
+        attrs: AttributeSet?,
+        defStyleAttr: Int,
+        defStyleRes: Int,
+    ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+    override fun setShouldBlockVisibilityChanges(block: Boolean) {
+        delegate.setShouldBlockVisibilityChanges(block)
+    }
+
+    override fun setVisibility(visibility: Int) {
+        delegate.setVisibility(visibility)
+    }
+
+    override fun setTransitionVisibility(visibility: Int) {
+        delegate.setTransitionVisibility(visibility)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index a996699..48bef97 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -22,25 +22,19 @@
 import android.content.Context;
 import android.hardware.SensorPrivacyManager;
 import android.os.Handler;
-import android.os.PowerManager;
 
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardViewController;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.doze.DozeHost;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dagger.MediaModule;
 import com.android.systemui.navigationbar.gestural.GestureModule;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.EnhancedEstimates;
 import com.android.systemui.power.dagger.PowerModule;
 import com.android.systemui.qs.dagger.QSModule;
 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
@@ -62,8 +56,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BatteryControllerImpl;
+import com.android.systemui.statusbar.policy.AospPolicyModule;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
@@ -97,6 +90,7 @@
  * SystemUI code that variants of SystemUI _must_ include to function correctly.
  */
 @Module(includes = {
+        AospPolicyModule.class,
         GestureModule.class,
         MediaModule.class,
         PowerModule.class,
@@ -121,30 +115,6 @@
 
     @Provides
     @SysUISingleton
-    static BatteryController provideBatteryController(
-            Context context,
-            EnhancedEstimates enhancedEstimates,
-            PowerManager powerManager,
-            BroadcastDispatcher broadcastDispatcher,
-            DemoModeController demoModeController,
-            DumpManager dumpManager,
-            @Main Handler mainHandler,
-            @Background Handler bgHandler) {
-        BatteryController bC = new BatteryControllerImpl(
-                context,
-                enhancedEstimates,
-                powerManager,
-                broadcastDispatcher,
-                demoModeController,
-                dumpManager,
-                mainHandler,
-                bgHandler);
-        bC.init();
-        return bC;
-    }
-
-    @Provides
-    @SysUISingleton
     static SensorPrivacyController provideSensorPrivacyController(
             SensorPrivacyManager sensorPrivacyManager) {
         SensorPrivacyController spC = new SensorPrivacyControllerImpl(sensorPrivacyManager);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
index dfa3bcd..fb4fc92 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
@@ -16,12 +16,14 @@
 
 package com.android.systemui.flags
 
+import android.util.Dumpable
+
 /**
  * Class to manage simple DeviceConfig-based feature flags.
  *
  * See [Flags] for instructions on defining new flags.
  */
-interface FeatureFlags : FlagListenable {
+interface FeatureFlags : FlagListenable, Dumpable {
     /** Returns a boolean value for the given flag.  */
     fun isEnabled(flag: UnreleasedFlag): Boolean
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index b4b8795..3adeeac 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -30,7 +30,6 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.os.Bundle;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.util.Log;
@@ -38,22 +37,15 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.commandline.Command;
-import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.jetbrains.annotations.NotNull;
 
 import java.io.PrintWriter;
-import java.lang.reflect.Field;
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.TreeMap;
@@ -76,10 +68,9 @@
  * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
  */
 @SysUISingleton
-public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
-    private static final String TAG = "SysUIFlags";
+public class FeatureFlagsDebug implements FeatureFlags {
+    static final String TAG = "SysUIFlags";
     static final String ALL_FLAGS = "all_flags";
-    private static final String FLAG_COMMAND = "flag";
 
     private final FlagManager mFlagManager;
     private final SecureSettings mSecureSettings;
@@ -90,7 +81,7 @@
     private final Map<Integer, Flag<?>> mAllFlags;
     private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
     private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
-    private final IStatusBarService mBarService;
+    private final Restarter mRestarter;
 
     @Inject
     public FeatureFlagsDebug(
@@ -99,12 +90,10 @@
             SecureSettings secureSettings,
             SystemPropertiesHelper systemProperties,
             @Main Resources resources,
-            DumpManager dumpManager,
             DeviceConfigProxy deviceConfigProxy,
             ServerFlagReader serverFlagReader,
             @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
-            CommandRegistry commandRegistry,
-            IStatusBarService barService) {
+            Restarter barService) {
         mFlagManager = flagManager;
         mSecureSettings = secureSettings;
         mResources = resources;
@@ -112,7 +101,7 @@
         mDeviceConfigProxy = deviceConfigProxy;
         mServerFlagReader = serverFlagReader;
         mAllFlags = allFlags;
-        mBarService = barService;
+        mRestarter = barService;
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_SET_FLAG);
@@ -121,8 +110,6 @@
         flagManager.setClearCacheAction(this::removeFromCache);
         context.registerReceiver(mReceiver, filter, null, null,
                 Context.RECEIVER_EXPORTED_UNAUDITED);
-        dumpManager.registerDumpable(TAG, this);
-        commandRegistry.registerCommand(FLAG_COMMAND, FlagCommand::new);
     }
 
     @Override
@@ -267,7 +254,7 @@
         mFlagManager.dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
     }
 
-    private <T> void eraseFlag(Flag<T> flag) {
+    <T> void eraseFlag(Flag<T> flag) {
         if (flag instanceof SysPropFlag) {
             mSystemProperties.erase(((SysPropFlag<T>) flag).getName());
             dispatchListenersAndMaybeRestart(flag.getId(), this::restartAndroid);
@@ -320,13 +307,10 @@
             return;
         }
         Log.i(TAG, "Restarting Android");
-        try {
-            mBarService.restart();
-        } catch (RemoteException e) {
-        }
+        mRestarter.restart();
     }
 
-    private void setBooleanFlagInternal(Flag<?> flag, boolean value) {
+    void setBooleanFlagInternal(Flag<?> flag, boolean value) {
         if (flag instanceof BooleanFlag) {
             setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
         } else if (flag instanceof ResourceBooleanFlag) {
@@ -343,7 +327,7 @@
         }
     }
 
-    private void setStringFlagInternal(Flag<?> flag, String value) {
+    void setStringFlagInternal(Flag<?> flag, String value) {
         if (flag instanceof StringFlag) {
             setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE);
         } else if (flag instanceof ResourceStringFlag) {
@@ -477,154 +461,4 @@
                 + ": [length=" + value.length() + "] \"" + value + "\""));
     }
 
-    class FlagCommand implements Command {
-        private final List<String> mOnCommands = List.of("true", "on", "1", "enabled");
-        private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
-
-        @Override
-        public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) {
-            if (args.size() == 0) {
-                pw.println("Error: no flag id supplied");
-                help(pw);
-                pw.println();
-                printKnownFlags(pw);
-                return;
-            }
-
-            if (args.size() > 2) {
-                pw.println("Invalid number of arguments.");
-                help(pw);
-                return;
-            }
-
-            int id = 0;
-            try {
-                id = Integer.parseInt(args.get(0));
-                if (!mAllFlags.containsKey(id)) {
-                    pw.println("Unknown flag id: " + id);
-                    pw.println();
-                    printKnownFlags(pw);
-                    return;
-                }
-            } catch (NumberFormatException e) {
-                id = flagNameToId(args.get(0));
-                if (id == 0) {
-                    pw.println("Invalid flag. Must an integer id or flag name: " + args.get(0));
-                    return;
-                }
-            }
-            Flag<?> flag = mAllFlags.get(id);
-
-            String cmd = "";
-            if (args.size() == 2) {
-                cmd = args.get(1).toLowerCase();
-            }
-
-            if ("erase".equals(cmd) || "reset".equals(cmd)) {
-                eraseFlag(flag);
-                return;
-            }
-
-            boolean newValue = true;
-            if (args.size() == 1 || "toggle".equals(cmd)) {
-                boolean enabled = isBooleanFlagEnabled(flag);
-
-                if (args.size() == 1) {
-                    pw.println("Flag " + id + " is " + enabled);
-                    return;
-                }
-
-                newValue = !enabled;
-            } else {
-                newValue = mOnCommands.contains(cmd);
-                if (!newValue && !mOffCommands.contains(cmd)) {
-                    pw.println("Invalid on/off argument supplied");
-                    help(pw);
-                    return;
-                }
-            }
-
-            pw.flush();  // Next command will restart sysui, so flush before we do so.
-            setBooleanFlagInternal(flag, newValue);
-        }
-
-        @Override
-        public void help(PrintWriter pw) {
-            pw.println(
-                    "Usage: adb shell cmd statusbar flag <id> "
-                            + "[true|false|1|0|on|off|enable|disable|toggle|erase|reset]");
-            pw.println("The id can either be a numeric integer or the corresponding field name");
-            pw.println(
-                    "If no argument is supplied after the id, the flags runtime value is output");
-        }
-
-        private boolean isBooleanFlagEnabled(Flag<?> flag) {
-            if (flag instanceof ReleasedFlag) {
-                return isEnabled((ReleasedFlag) flag);
-            } else if (flag instanceof UnreleasedFlag) {
-                return isEnabled((UnreleasedFlag) flag);
-            } else if (flag instanceof ResourceBooleanFlag) {
-                return isEnabled((ResourceBooleanFlag) flag);
-            } else if (flag instanceof SysPropFlag) {
-                return isEnabled((SysPropBooleanFlag) flag);
-            }
-
-            return false;
-        }
-
-        private int flagNameToId(String flagName) {
-            List<Field> fields = Flags.getFlagFields();
-            for (Field field : fields) {
-                if (flagName.equals(field.getName())) {
-                    return fieldToId(field);
-                }
-            }
-
-            return 0;
-        }
-
-        private int fieldToId(Field field) {
-            try {
-                Flag<?> flag = (Flag<?>) field.get(null);
-                return flag.getId();
-            } catch (IllegalAccessException e) {
-                // no-op
-            }
-
-            return 0;
-        }
-
-        private void printKnownFlags(PrintWriter pw) {
-            List<Field> fields = Flags.getFlagFields();
-
-            int longestFieldName = 0;
-            for (Field field : fields) {
-                longestFieldName = Math.max(longestFieldName, field.getName().length());
-            }
-
-            pw.println("Known Flags:");
-            pw.print("Flag Name");
-            for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) {
-                pw.print(" ");
-            }
-            pw.println("ID   Enabled?");
-            for (int i = 0; i < longestFieldName; i++) {
-                pw.print("=");
-            }
-            pw.println(" ==== ========");
-            for (Field field : fields) {
-                int id = fieldToId(field);
-                if (id == 0 || !mAllFlags.containsKey(id)) {
-                    continue;
-                }
-                pw.print(field.getName());
-                int fieldWidth = field.getName().length();
-                for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) {
-                    pw.print(" ");
-                }
-                pw.printf("%-4d ", id);
-                pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
new file mode 100644
index 0000000..c0e3021
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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.flags
+
+import android.content.Context
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+
+class FeatureFlagsDebugStartable
+@Inject
+constructor(
+    @Application context: Context,
+    dumpManager: DumpManager,
+    private val commandRegistry: CommandRegistry,
+    private val flagCommand: FlagCommand,
+    featureFlags: FeatureFlags
+) : CoreStartable(context) {
+
+    init {
+        dumpManager.registerDumpable(FeatureFlagsDebug.TAG) { pw, args ->
+            featureFlags.dump(pw, args)
+        }
+    }
+
+    override fun start() {
+        commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
+    }
+}
+
+@Module
+abstract class FeatureFlagsDebugStartableModule {
+    @Binds
+    @IntoMap
+    @ClassKey(FeatureFlagsDebugStartable::class)
+    abstract fun bind(impl: FeatureFlagsDebugStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 049b17d..40a8a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -24,10 +24,8 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.util.DeviceConfigProxy;
 
 import org.jetbrains.annotations.NotNull;
@@ -44,27 +42,26 @@
  * how to set flags.
  */
 @SysUISingleton
-public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
+public class FeatureFlagsRelease implements FeatureFlags {
+    static final String TAG = "SysUIFlags";
+
     private final Resources mResources;
     private final SystemPropertiesHelper mSystemProperties;
     private final DeviceConfigProxy mDeviceConfigProxy;
     private final ServerFlagReader mServerFlagReader;
     SparseBooleanArray mBooleanCache = new SparseBooleanArray();
     SparseArray<String> mStringCache = new SparseArray<>();
-    private boolean mInited;
 
     @Inject
     public FeatureFlagsRelease(
             @Main Resources resources,
             SystemPropertiesHelper systemProperties,
             DeviceConfigProxy deviceConfigProxy,
-            ServerFlagReader serverFlagReader,
-            DumpManager dumpManager) {
+            ServerFlagReader serverFlagReader) {
         mResources = resources;
         mSystemProperties = systemProperties;
         mDeviceConfigProxy = deviceConfigProxy;
         mServerFlagReader = serverFlagReader;
-        dumpManager.registerDumpable("SysUIFlags", this);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
new file mode 100644
index 0000000..f138f1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 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.flags
+
+import android.content.Context
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+
+class FeatureFlagsReleaseStartable
+@Inject
+constructor(@Application context: Context, dumpManager: DumpManager, featureFlags: FeatureFlags) :
+    CoreStartable(context) {
+
+    init {
+        dumpManager.registerDumpable(FeatureFlagsRelease.TAG) { pw, args ->
+            featureFlags.dump(pw, args)
+        }
+    }
+
+    override fun start() {
+        // no-op
+    }
+}
+
+@Module
+abstract class FeatureFlagsReleaseStartableModule {
+    @Binds
+    @IntoMap
+    @ClassKey(FeatureFlagsReleaseStartable::class)
+    abstract fun bind(impl: FeatureFlagsReleaseStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
new file mode 100644
index 0000000..4d25431
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2022 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.flags;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.statusbar.commandline.Command;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * A {@link Command} used to flip flags in SystemUI.
+ */
+public class FlagCommand implements Command {
+    public static final String FLAG_COMMAND = "flag";
+
+    private final List<String> mOnCommands = List.of("true", "on", "1", "enabled");
+    private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
+    private final FeatureFlagsDebug mFeatureFlags;
+    private final Map<Integer, Flag<?>> mAllFlags;
+
+    @Inject
+    FlagCommand(
+            FeatureFlagsDebug featureFlags,
+            @Named(FeatureFlagsDebug.ALL_FLAGS) Map<Integer, Flag<?>> allFlags
+    ) {
+        mFeatureFlags = featureFlags;
+        mAllFlags = allFlags;
+    }
+
+    @Override
+    public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) {
+        if (args.size() == 0) {
+            pw.println("Error: no flag id supplied");
+            help(pw);
+            pw.println();
+            printKnownFlags(pw);
+            return;
+        }
+
+        if (args.size() > 2) {
+            pw.println("Invalid number of arguments.");
+            help(pw);
+            return;
+        }
+
+        int id = 0;
+        try {
+            id = Integer.parseInt(args.get(0));
+            if (!mAllFlags.containsKey(id)) {
+                pw.println("Unknown flag id: " + id);
+                pw.println();
+                printKnownFlags(pw);
+                return;
+            }
+        } catch (NumberFormatException e) {
+            id = flagNameToId(args.get(0));
+            if (id == 0) {
+                pw.println("Invalid flag. Must an integer id or flag name: " + args.get(0));
+                return;
+            }
+        }
+        Flag<?> flag = mAllFlags.get(id);
+
+        String cmd = "";
+        if (args.size() == 2) {
+            cmd = args.get(1).toLowerCase();
+        }
+
+        if ("erase".equals(cmd) || "reset".equals(cmd)) {
+            mFeatureFlags.eraseFlag(flag);
+            return;
+        }
+
+        boolean newValue = true;
+        if (args.size() == 1 || "toggle".equals(cmd)) {
+            boolean enabled = isBooleanFlagEnabled(flag);
+
+            if (args.size() == 1) {
+                pw.println("Flag " + id + " is " + enabled);
+                return;
+            }
+
+            newValue = !enabled;
+        } else {
+            newValue = mOnCommands.contains(cmd);
+            if (!newValue && !mOffCommands.contains(cmd)) {
+                pw.println("Invalid on/off argument supplied");
+                help(pw);
+                return;
+            }
+        }
+
+        pw.flush();  // Next command will restart sysui, so flush before we do so.
+        mFeatureFlags.setBooleanFlagInternal(flag, newValue);
+    }
+
+    @Override
+    public void help(PrintWriter pw) {
+        pw.println(
+                "Usage: adb shell cmd statusbar flag <id> "
+                        + "[true|false|1|0|on|off|enable|disable|toggle|erase|reset]");
+        pw.println("The id can either be a numeric integer or the corresponding field name");
+        pw.println(
+                "If no argument is supplied after the id, the flags runtime value is output");
+    }
+
+    private boolean isBooleanFlagEnabled(Flag<?> flag) {
+        if (flag instanceof ReleasedFlag) {
+            return mFeatureFlags.isEnabled((ReleasedFlag) flag);
+        } else if (flag instanceof UnreleasedFlag) {
+            return mFeatureFlags.isEnabled((UnreleasedFlag) flag);
+        } else if (flag instanceof ResourceBooleanFlag) {
+            return mFeatureFlags.isEnabled((ResourceBooleanFlag) flag);
+        } else if (flag instanceof SysPropFlag) {
+            return mFeatureFlags.isEnabled((SysPropBooleanFlag) flag);
+        }
+
+        return false;
+    }
+
+    private int flagNameToId(String flagName) {
+        List<Field> fields = Flags.getFlagFields();
+        for (Field field : fields) {
+            if (flagName.equals(field.getName())) {
+                return fieldToId(field);
+            }
+        }
+
+        return 0;
+    }
+
+    private int fieldToId(Field field) {
+        try {
+            Flag<?> flag = (Flag<?>) field.get(null);
+            return flag.getId();
+        } catch (IllegalAccessException e) {
+            // no-op
+        }
+
+        return 0;
+    }
+
+    private void printKnownFlags(PrintWriter pw) {
+        List<Field> fields = Flags.getFlagFields();
+
+        int longestFieldName = 0;
+        for (Field field : fields) {
+            longestFieldName = Math.max(longestFieldName, field.getName().length());
+        }
+
+        pw.println("Known Flags:");
+        pw.print("Flag Name");
+        for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) {
+            pw.print(" ");
+        }
+        pw.println("ID   Enabled?");
+        for (int i = 0; i < longestFieldName; i++) {
+            pw.print("=");
+        }
+        pw.println(" ==== ========");
+        for (Field field : fields) {
+            int id = fieldToId(field);
+            if (id == 0 || !mAllFlags.containsKey(id)) {
+                continue;
+            }
+            pw.print(field.getName());
+            int fieldWidth = field.getName().length();
+            for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) {
+                pw.print(" ");
+            }
+            pw.printf("%-4d ", id);
+            pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 2634b03..fcf11ef 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -218,7 +218,7 @@
 
     /***************************************/
     // 900 - media
-    public static final UnreleasedFlag MEDIA_TAP_TO_TRANSFER = new UnreleasedFlag(900);
+    public static final ReleasedFlag MEDIA_TAP_TO_TRANSFER = new ReleasedFlag(900);
     public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901);
     public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903);
     public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
new file mode 100644
index 0000000..8f095a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 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.flags
+
+interface Restarter {
+    fun restart()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 2cd564f..9dd18b2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -886,7 +886,8 @@
                     interactedSubcardRank,
                     interactedSubcardCardinality,
                     receivedLatencyMillis,
-                    null // Media cards cannot have subcards.
+                    null, // Media cards cannot have subcards.
+                    null // Media cards don't have dimensions today.
             )
             /* ktlint-disable max-line-length */
             if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AospPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AospPolicyModule.java
new file mode 100644
index 0000000..ba5fa1d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AospPolicyModule.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.os.Handler;
+import android.os.PowerManager;
+
+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.DemoModeController;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.power.EnhancedEstimates;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * com.android.systemui.statusbar.policy related providers that others may want to override.
+ */
+@Module
+public class AospPolicyModule {
+    @Provides
+    @SysUISingleton
+    static BatteryController provideBatteryController(
+            Context context,
+            EnhancedEstimates enhancedEstimates,
+            PowerManager powerManager,
+            BroadcastDispatcher broadcastDispatcher,
+            DemoModeController demoModeController,
+            DumpManager dumpManager,
+            @Main Handler mainHandler,
+            @Background Handler bgHandler) {
+        BatteryController bC = new BatteryControllerImpl(
+                context,
+                enhancedEstimates,
+                powerManager,
+                broadcastDispatcher,
+                demoModeController,
+                dumpManager,
+                mainHandler,
+                bgHandler);
+        bC.init();
+        return bC;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
index 1692656..af39eee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.user.data.source.UserRecord
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.UserInteractor
-import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
 import dagger.Lazy
 import java.io.PrintWriter
@@ -118,7 +117,7 @@
         dialogShower: UserSwitchDialogController.DialogShower?
     ) {
         if (useInteractor) {
-            userInteractor.selectUser(userId)
+            userInteractor.selectUser(userId, dialogShower)
         } else {
             _oldImpl.onUserSelected(userId, dialogShower)
         }
@@ -203,11 +202,7 @@
         dialogShower: UserSwitchDialogController.DialogShower?,
     ) {
         if (useInteractor) {
-            if (LegacyUserDataHelper.isUser(record)) {
-                userInteractor.selectUser(record.resolveId())
-            } else {
-                userInteractor.executeAction(LegacyUserDataHelper.toUserActionModel(record))
-            }
+            userInteractor.onRecordSelected(record, dialogShower)
         } else {
             _oldImpl.onUserListItemClicked(record, dialogShower)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index adef182..a345d99 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -171,6 +171,10 @@
 
         @Override
         public void onColorsChanged(WallpaperColors wallpaperColors, int which, int userId) {
+            WallpaperColors currentColors = mCurrentColors.get(userId);
+            if (wallpaperColors != null && wallpaperColors.equals(currentColors)) {
+                return;
+            }
             boolean currentUser = userId == mUserTracker.getUserId();
             if (currentUser && !mAcceptColorEvents
                     && mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP) {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 00ed3d6..3ce5ca3 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -22,25 +22,20 @@
 import android.content.Context;
 import android.hardware.SensorPrivacyManager;
 import android.os.Handler;
-import android.os.PowerManager;
 
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardViewController;
-import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.ReferenceSystemUIModule;
 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.DemoModeController;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.doze.DozeHost;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.gestural.GestureModule;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.EnhancedEstimates;
 import com.android.systemui.power.dagger.PowerModule;
 import com.android.systemui.privacy.MediaProjectionPrivacyItemMonitor;
 import com.android.systemui.privacy.PrivacyItemMonitor;
@@ -64,8 +59,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BatteryControllerImpl;
+import com.android.systemui.statusbar.policy.AospPolicyModule;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
@@ -86,18 +80,21 @@
 import dagger.multibindings.IntoSet;
 
 /**
- * A dagger module for injecting default implementations of components of System UI that may be
- * overridden by the System UI implementation.
+ * A TV specific version of {@link ReferenceSystemUIModule}.
+ *
+ * Code here should be specific to the TV variant of SystemUI and will not be included in other
+ * variants of SystemUI.
  */
-@Module(includes = {
-            GestureModule.class,
-            PowerModule.class,
-            QSModule.class,
-            ReferenceScreenshotModule.class,
-            VolumeModule.class,
-        },
-        subcomponents = {
-        })
+@Module(
+        includes = {
+                AospPolicyModule.class,
+                GestureModule.class,
+                PowerModule.class,
+                QSModule.class,
+                ReferenceScreenshotModule.class,
+                VolumeModule.class,
+        }
+)
 public abstract class TvSystemUIModule {
 
     @SysUISingleton
@@ -114,21 +111,6 @@
 
     @Provides
     @SysUISingleton
-    static BatteryController provideBatteryController(Context context,
-            EnhancedEstimates enhancedEstimates, PowerManager powerManager,
-            BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController,
-            DumpManager dumpManager,
-            @Main Handler mainHandler, @Background Handler bgHandler) {
-        BatteryController bC = new BatteryControllerImpl(context, enhancedEstimates, powerManager,
-                broadcastDispatcher, demoModeController,
-                dumpManager,
-                mainHandler, bgHandler);
-        bC.init();
-        return bC;
-    }
-
-    @Provides
-    @SysUISingleton
     static SensorPrivacyController provideSensorPrivacyController(
             SensorPrivacyManager sensorPrivacyManager) {
         SensorPrivacyController spC = new SensorPrivacyControllerImpl(sensorPrivacyManager);
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 3014f39..919e699 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -220,7 +220,7 @@
             val result = withContext(backgroundDispatcher) { manager.aliveUsers }
 
             if (result != null) {
-                _userInfos.value = result
+                _userInfos.value = result.sortedBy { it.creationTime }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index a84238c..142a328 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.data.repository.UserRepository
@@ -390,9 +391,24 @@
         guestUserInteractor.onDeviceBootCompleted()
     }
 
+    /** Switches to the user or executes the action represented by the given record. */
+    fun onRecordSelected(
+        record: UserRecord,
+        dialogShower: UserSwitchDialogController.DialogShower? = null,
+    ) {
+        if (LegacyUserDataHelper.isUser(record)) {
+            // It's safe to use checkNotNull around record.info because isUser only returns true
+            // if record.info is not null.
+            selectUser(checkNotNull(record.info).id, dialogShower)
+        } else {
+            executeAction(LegacyUserDataHelper.toUserActionModel(record), dialogShower)
+        }
+    }
+
     /** Switches to the user with the given user ID. */
     fun selectUser(
         newlySelectedUserId: Int,
+        dialogShower: UserSwitchDialogController.DialogShower? = null,
     ) {
         if (isNewImpl) {
             val currentlySelectedUserInfo = repository.getSelectedUserInfo()
@@ -428,22 +444,28 @@
                 return
             }
 
+            dialogShower?.dismiss()
+
             switchUser(newlySelectedUserId)
         } else {
-            controller.onUserSelected(newlySelectedUserId, /* dialogShower= */ null)
+            controller.onUserSelected(newlySelectedUserId, dialogShower)
         }
     }
 
     /** Executes the given action. */
-    fun executeAction(action: UserActionModel) {
+    fun executeAction(
+        action: UserActionModel,
+        dialogShower: UserSwitchDialogController.DialogShower? = null,
+    ) {
         if (isNewImpl) {
             when (action) {
                 UserActionModel.ENTER_GUEST_MODE ->
                     guestUserInteractor.createAndSwitchTo(
                         this::showDialog,
                         this::dismissDialog,
-                        this::selectUser,
-                    )
+                    ) { userId ->
+                        selectUser(userId, dialogShower)
+                    }
                 UserActionModel.ADD_USER -> {
                     val currentUser = repository.getSelectedUserInfo()
                     showDialog(
@@ -575,7 +597,7 @@
     }
 
     private fun switchUser(userId: Int) {
-        // TODO(b/246631653): track jank and lantecy like in the old impl.
+        // TODO(b/246631653): track jank and latency like in the old impl.
         refreshUsersScheduler.pause()
         try {
             activityManager.switchUser(userId)
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
index db35437e..ecb365f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
@@ -34,9 +34,26 @@
     private final String mTag = getClass().getSimpleName();
 
     private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
-    private boolean mIsConditionMet = false;
+    private final boolean mOverriding;
+    private Boolean mIsConditionMet;
     private boolean mStarted = false;
-    private boolean mOverriding = false;
+
+    /**
+     * By default, conditions have an initial value of false and are not overriding.
+     */
+    public Condition() {
+        this(false, false);
+    }
+
+    /**
+     * Constructor for specifying initial state and overriding condition attribute.
+     * @param initialConditionMet Initial state of the condition.
+     * @param overriding Whether this condition overrides others.
+     */
+    protected Condition(Boolean initialConditionMet, boolean overriding) {
+        mIsConditionMet = initialConditionMet;
+        mOverriding = overriding;
+    }
 
     /**
      * Starts monitoring the condition.
@@ -49,14 +66,6 @@
     protected abstract void stop();
 
     /**
-     * Sets whether this condition's value overrides others in determining the overall state.
-     */
-    public void setOverriding(boolean overriding) {
-        mOverriding = overriding;
-        updateCondition(mIsConditionMet);
-    }
-
-    /**
      * Returns whether the current condition overrides
      */
     public boolean isOverridingCondition() {
@@ -110,13 +119,31 @@
      * @param isConditionMet True if the condition has been fulfilled. False otherwise.
      */
     protected void updateCondition(boolean isConditionMet) {
-        if (mIsConditionMet == isConditionMet) {
+        if (mIsConditionMet != null && mIsConditionMet == isConditionMet) {
             return;
         }
 
         if (shouldLog()) Log.d(mTag, "updating condition to " + isConditionMet);
         mIsConditionMet = isConditionMet;
+        sendUpdate();
+    }
 
+    /**
+     * Clears the set condition value. This is purposefully separate from
+     * {@link #updateCondition(boolean)} to avoid confusion around {@code null} values.
+     */
+    protected void clearCondition() {
+        if (mIsConditionMet == null) {
+            return;
+        }
+
+        if (shouldLog()) Log.d(mTag, "clearing condition");
+
+        mIsConditionMet = null;
+        sendUpdate();
+    }
+
+    private void sendUpdate() {
         final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
         while (iterator.hasNext()) {
             final Callback cb = iterator.next().get();
@@ -128,8 +155,21 @@
         }
     }
 
+    /**
+     * Returns whether the condition is set. This method should be consulted to understand the
+     * value of {@link #isConditionMet()}.
+     * @return {@code true} if value is present, {@code false} otherwise.
+     */
+    public boolean isConditionSet() {
+        return mIsConditionMet != null;
+    }
+
+    /**
+     * Returns whether the condition has been met. Note that this method will return {@code false}
+     * if the condition is not set as well.
+     */
     public boolean isConditionMet() {
-        return mIsConditionMet;
+        return Boolean.TRUE.equals(mIsConditionMet);
     }
 
     private boolean shouldLog() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index dac8a0b..4824f67 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -57,12 +57,16 @@
         }
 
         public void update() {
+            // Only consider set conditions.
+            final Collection<Condition> setConditions = mSubscription.mConditions.stream()
+                    .filter(Condition::isConditionSet).collect(Collectors.toSet());
+
             // Overriding conditions do not override each other
-            final Collection<Condition> overridingConditions = mSubscription.mConditions.stream()
+            final Collection<Condition> overridingConditions = setConditions.stream()
                     .filter(Condition::isOverridingCondition).collect(Collectors.toSet());
 
             final Collection<Condition> targetCollection = overridingConditions.isEmpty()
-                    ? mSubscription.mConditions : overridingConditions;
+                    ? setConditions : overridingConditions;
 
             final boolean newAllConditionsMet = targetCollection.isEmpty() ? true : targetCollection
                     .stream()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 4511193..20a82c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -21,15 +21,10 @@
 import android.content.pm.PackageManager.NameNotFoundException
 import android.content.res.Resources
 import android.test.suitebuilder.annotation.SmallTest
-import com.android.internal.statusbar.IStatusBarService
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.util.DeviceConfigProxyFake
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.withArgCaptor
@@ -46,18 +41,16 @@
 import org.mockito.Mock
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.anyString
-import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 /**
- * NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
- * overriding, and should never return any value other than the one provided as the default.
+ * NOTE: This test is for the version of FeatureFlagManager in src-debug, which allows overriding
+ * the default.
  */
 @SmallTest
 class FeatureFlagsDebugTest : SysuiTestCase() {
@@ -68,10 +61,8 @@
     @Mock private lateinit var secureSettings: SecureSettings
     @Mock private lateinit var systemProperties: SystemPropertiesHelper
     @Mock private lateinit var resources: Resources
-    @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var commandRegistry: CommandRegistry
-    @Mock private lateinit var barService: IStatusBarService
-    @Mock private lateinit var pw: PrintWriter
+    @Mock private lateinit var restarter: Restarter
     private val flagMap = mutableMapOf<Int, Flag<*>>()
     private lateinit var broadcastReceiver: BroadcastReceiver
     private lateinit var clearCacheAction: Consumer<Int>
@@ -92,12 +83,10 @@
             secureSettings,
             systemProperties,
             resources,
-            dumpManager,
             deviceConfig,
             serverFlagReader,
             flagMap,
-            commandRegistry,
-            barService
+            restarter
         )
         verify(flagManager).onSettingsChangedAction = any()
         broadcastReceiver = withArgCaptor {
@@ -366,53 +355,6 @@
     }
 
     @Test
-    fun statusBarCommand_IsRegistered() {
-        verify(commandRegistry).registerCommand(anyString(), any())
-    }
-
-    @Test
-    fun noOpCommand() {
-        val cmd = captureCommand()
-
-        cmd.execute(pw, ArrayList())
-        verify(pw, atLeastOnce()).println()
-        verify(flagManager).readFlagValue<Boolean>(eq(1), any())
-        verifyZeroInteractions(secureSettings)
-    }
-
-    @Test
-    fun readFlagCommand() {
-        addFlag(UnreleasedFlag(1))
-        val cmd = captureCommand()
-        cmd.execute(pw, listOf("1"))
-        verify(flagManager).readFlagValue<Boolean>(eq(1), any())
-    }
-
-    @Test
-    fun setFlagCommand() {
-        addFlag(UnreleasedFlag(1))
-        val cmd = captureCommand()
-        cmd.execute(pw, listOf("1", "on"))
-        verifyPutData(1, "{\"type\":\"boolean\",\"value\":true}")
-    }
-
-    @Test
-    fun toggleFlagCommand() {
-        addFlag(ReleasedFlag(1))
-        val cmd = captureCommand()
-        cmd.execute(pw, listOf("1", "toggle"))
-        verifyPutData(1, "{\"type\":\"boolean\",\"value\":false}", 2)
-    }
-
-    @Test
-    fun eraseFlagCommand() {
-        addFlag(ReleasedFlag(1))
-        val cmd = captureCommand()
-        cmd.execute(pw, listOf("1", "erase"))
-        verify(secureSettings).putStringForUser(eq("key-1"), eq(""), anyInt())
-    }
-
-    @Test
     fun dumpFormat() {
         val flag1 = ReleasedFlag(1)
         val flag2 = ResourceBooleanFlag(2, 1002)
@@ -471,13 +413,6 @@
         return flag
     }
 
-    private fun captureCommand(): Command {
-        val captor = argumentCaptor<Function0<Command>>()
-        verify(commandRegistry).registerCommand(anyString(), capture(captor))
-
-        return captor.value.invoke()
-    }
-
     private fun dumpToString(): String {
         val sw = StringWriter()
         val pw = PrintWriter(sw)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index e94b520..575c142 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -19,17 +19,12 @@
 import android.content.res.Resources
 import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
 import com.android.systemui.util.DeviceConfigProxyFake
-import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
-import org.junit.After
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
@@ -43,7 +38,6 @@
 
     @Mock private lateinit var mResources: Resources
     @Mock private lateinit var mSystemProperties: SystemPropertiesHelper
-    @Mock private lateinit var mDumpManager: DumpManager
     private val serverFlagReader = ServerFlagReaderFake()
 
     private val deviceConfig = DeviceConfigProxyFake()
@@ -55,15 +49,7 @@
             mResources,
             mSystemProperties,
             deviceConfig,
-            serverFlagReader,
-            mDumpManager)
-    }
-
-    @After
-    fun onFinished() {
-        // The dump manager should be registered with even for the release version, but that's it.
-        verify(mDumpManager).registerDumpable(any(), any())
-        verifyNoMoreInteractions(mDumpManager)
+            serverFlagReader)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
new file mode 100644
index 0000000..4c61138
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 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.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import java.io.PrintWriter
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class FlagCommandTest : SysuiTestCase() {
+
+    @Mock private lateinit var featureFlags: FeatureFlagsDebug
+    @Mock private lateinit var pw: PrintWriter
+    private val flagMap = mutableMapOf<Int, Flag<*>>()
+    private val flagA = UnreleasedFlag(500)
+    private val flagB = ReleasedFlag(501)
+
+    private lateinit var cmd: FlagCommand
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(featureFlags.isEnabled(any(UnreleasedFlag::class.java))).thenReturn(false)
+        whenever(featureFlags.isEnabled(any(ReleasedFlag::class.java))).thenReturn(true)
+        flagMap.put(flagA.id, flagA)
+        flagMap.put(flagB.id, flagB)
+
+        cmd = FlagCommand(featureFlags, flagMap)
+    }
+
+    @Test
+    fun noOpCommand() {
+        cmd.execute(pw, ArrayList())
+        Mockito.verify(pw, Mockito.atLeastOnce()).println()
+        Mockito.verify(featureFlags).isEnabled(flagA)
+        Mockito.verify(featureFlags).isEnabled(flagB)
+    }
+
+    @Test
+    fun readFlagCommand() {
+        cmd.execute(pw, listOf(flagA.id.toString()))
+        Mockito.verify(featureFlags).isEnabled(flagA)
+    }
+
+    @Test
+    fun setFlagCommand() {
+        cmd.execute(pw, listOf(flagB.id.toString(), "on"))
+        Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, true)
+    }
+
+    @Test
+    fun toggleFlagCommand() {
+        cmd.execute(pw, listOf(flagB.id.toString(), "toggle"))
+        Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, false)
+    }
+
+    @Test
+    fun eraseFlagCommand() {
+        cmd.execute(pw, listOf(flagA.id.toString(), "erase"))
+        Mockito.verify(featureFlags).eraseFlag(flagA)
+    }
+}
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 50259b5..2a93fff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -885,4 +885,31 @@
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
                 .isEqualTo(new OverlayIdentifier("ff00ff00"));
     }
+
+    // Regression test for b/234603929, where a reboot would generate a wallpaper colors changed
+    // event for the already-set colors that would then set the theme incorrectly on screen sleep.
+    @Test
+    public void onWallpaperColorsSetToSame_keepsTheme() {
+        // Set initial colors and verify.
+        WallpaperColors startingColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+        WallpaperColors sameColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+        mColorsListener.getValue().onColorsChanged(startingColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        clearInvocations(mThemeOverlayApplier);
+
+        // Set to the same colors.
+        mColorsListener.getValue().onColorsChanged(sameColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
+        verify(mThemeOverlayApplier, never())
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+
+        // Verify that no change resulted.
+        mWakefulnessLifecycleObserver.getValue().onFinishedGoingToSleep();
+        verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
+                any());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
index 4a8e055..d951f36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
@@ -120,6 +120,27 @@
         assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
     }
 
+    @Test
+    fun `refreshUsers - sorts by creation time`() = runSelfCancelingTest {
+        underTest = create(this)
+        val unsortedUsers =
+            setUpUsers(
+                count = 3,
+                selectedIndex = 0,
+            )
+        unsortedUsers[0].creationTime = 900
+        unsortedUsers[1].creationTime = 700
+        unsortedUsers[2].creationTime = 999
+        val expectedUsers = listOf(unsortedUsers[1], unsortedUsers[0], unsortedUsers[2])
+        var userInfos: List<UserInfo>? = null
+        var selectedUserInfo: UserInfo? = null
+        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+
+        underTest.refreshUsers()
+        assertThat(userInfos).isEqualTo(expectedUsers)
+    }
+
     private fun setUpUsers(
         count: Int,
         hasGuest: Boolean = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
index 3d5695a..37c378c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
@@ -47,7 +47,9 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 
 @SmallTest
@@ -73,6 +75,66 @@
     }
 
     @Test
+    fun `onRecordSelected - user`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
+
+            verify(dialogShower).dismiss()
+            verify(activityManager).switchUser(userInfos[1].id)
+            Unit
+        }
+
+    @Test
+    fun `onRecordSelected - switch to guest user`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            underTest.onRecordSelected(UserRecord(info = userInfos.last()))
+
+            verify(activityManager).switchUser(userInfos.last().id)
+            Unit
+        }
+
+    @Test
+    fun `onRecordSelected - enter guest mode`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = false)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+            val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+            whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+
+            underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
+
+            verify(dialogShower).dismiss()
+            verify(manager).createGuest(any())
+            Unit
+        }
+
+    @Test
+    fun `onRecordSelected - action`() =
+        runBlocking(IMMEDIATE) {
+            val userInfos = createUserInfos(count = 3, includeGuest = true)
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
+
+            verify(dialogShower, never()).dismiss()
+            verify(activityStarter).startActivity(any(), anyBoolean())
+        }
+
+    @Test
     fun `users - switcher enabled`() =
         runBlocking(IMMEDIATE) {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
@@ -336,10 +398,14 @@
             var dialogRequest: ShowDialogRequestModel? = null
             val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
 
-            underTest.selectUser(newlySelectedUserId = guestUserInfo.id)
+            underTest.selectUser(
+                newlySelectedUserId = guestUserInfo.id,
+                dialogShower = dialogShower,
+            )
 
             assertThat(dialogRequest)
                 .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+            verify(dialogShower, never()).dismiss()
             job.cancel()
         }
 
@@ -355,10 +421,11 @@
             var dialogRequest: ShowDialogRequestModel? = null
             val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
 
-            underTest.selectUser(newlySelectedUserId = userInfos[0].id)
+            underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
 
             assertThat(dialogRequest)
                 .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+            verify(dialogShower, never()).dismiss()
             job.cancel()
         }
 
@@ -372,10 +439,11 @@
             var dialogRequest: ShowDialogRequestModel? = null
             val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
 
-            underTest.selectUser(newlySelectedUserId = userInfos[1].id)
+            underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
 
             assertThat(dialogRequest).isNull()
             verify(activityManager).switchUser(userInfos[1].id)
+            verify(dialogShower).dismiss()
             job.cancel()
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 8465f4f..1680c36c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
@@ -46,6 +47,7 @@
     @Mock protected lateinit var deviceProvisionedController: DeviceProvisionedController
     @Mock protected lateinit var devicePolicyManager: DevicePolicyManager
     @Mock protected lateinit var uiEventLogger: UiEventLogger
+    @Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower
 
     protected lateinit var underTest: UserInteractor
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
index 125b362..17d81c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
@@ -73,10 +73,16 @@
                 .addConditions(mConditions);
     }
 
+    private Condition createMockCondition() {
+        final Condition condition = Mockito.mock(Condition.class);
+        when(condition.isConditionSet()).thenReturn(true);
+        return condition;
+    }
+
     @Test
     public void testOverridingCondition() {
-        final Condition overridingCondition = Mockito.mock(Condition.class);
-        final Condition regularCondition = Mockito.mock(Condition.class);
+        final Condition overridingCondition = createMockCondition();
+        final Condition regularCondition = createMockCondition();
         final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);
 
         final Monitor.Callback referenceCallback = Mockito.mock(Monitor.Callback.class);
@@ -127,9 +133,9 @@
      */
     @Test
     public void testMultipleOverridingConditions() {
-        final Condition overridingCondition = Mockito.mock(Condition.class);
-        final Condition overridingCondition2 = Mockito.mock(Condition.class);
-        final Condition regularCondition = Mockito.mock(Condition.class);
+        final Condition overridingCondition = createMockCondition();
+        final Condition overridingCondition2 = createMockCondition();
+        final Condition regularCondition = createMockCondition();
         final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);
 
         final Monitor monitor = new Monitor(mExecutor);
@@ -340,4 +346,114 @@
         mExecutor.runAllReady();
         verify(callback).onConditionsChanged(true);
     }
+
+    @Test
+    public void clearCondition_shouldUpdateValue() {
+        mCondition1.fakeUpdateCondition(false);
+        mCondition2.fakeUpdateCondition(true);
+        mCondition3.fakeUpdateCondition(true);
+
+        final Monitor.Callback callback =
+                mock(Monitor.Callback.class);
+        mConditionMonitor.addSubscription(getDefaultBuilder(callback).build());
+        mExecutor.runAllReady();
+        verify(callback).onConditionsChanged(false);
+
+        mCondition1.clearCondition();
+        mExecutor.runAllReady();
+        verify(callback).onConditionsChanged(true);
+    }
+
+    @Test
+    public void unsetCondition_shouldNotAffectValue() {
+        final FakeCondition settableCondition = new FakeCondition(null, false);
+        mCondition1.fakeUpdateCondition(true);
+        mCondition2.fakeUpdateCondition(true);
+        mCondition3.fakeUpdateCondition(true);
+
+        final Monitor.Callback callback =
+                mock(Monitor.Callback.class);
+
+        mConditionMonitor.addSubscription(getDefaultBuilder(callback)
+                .addCondition(settableCondition)
+                .build());
+
+        mExecutor.runAllReady();
+        verify(callback).onConditionsChanged(true);
+    }
+
+    @Test
+    public void setUnsetCondition_shouldAffectValue() {
+        final FakeCondition settableCondition = new FakeCondition(null, false);
+        mCondition1.fakeUpdateCondition(true);
+        mCondition2.fakeUpdateCondition(true);
+        mCondition3.fakeUpdateCondition(true);
+
+        final Monitor.Callback callback =
+                mock(Monitor.Callback.class);
+
+        mConditionMonitor.addSubscription(getDefaultBuilder(callback)
+                .addCondition(settableCondition)
+                .build());
+
+        mExecutor.runAllReady();
+        verify(callback).onConditionsChanged(true);
+        clearInvocations(callback);
+
+        settableCondition.fakeUpdateCondition(false);
+        mExecutor.runAllReady();
+        verify(callback).onConditionsChanged(false);
+        clearInvocations(callback);
+
+
+        settableCondition.clearCondition();
+        mExecutor.runAllReady();
+        verify(callback).onConditionsChanged(true);
+    }
+
+    @Test
+    public void clearingOverridingCondition_shouldBeExcluded() {
+        final FakeCondition overridingCondition = new FakeCondition(true, true);
+        mCondition1.fakeUpdateCondition(false);
+        mCondition2.fakeUpdateCondition(false);
+        mCondition3.fakeUpdateCondition(false);
+
+        final Monitor.Callback callback =
+                mock(Monitor.Callback.class);
+
+        mConditionMonitor.addSubscription(getDefaultBuilder(callback)
+                .addCondition(overridingCondition)
+                .build());
+
+        mExecutor.runAllReady();
+        verify(callback).onConditionsChanged(true);
+        clearInvocations(callback);
+
+        overridingCondition.clearCondition();
+        mExecutor.runAllReady();
+        verify(callback).onConditionsChanged(false);
+    }
+
+    @Test
+    public void settingUnsetOverridingCondition_shouldBeIncluded() {
+        final FakeCondition overridingCondition = new FakeCondition(null, true);
+        mCondition1.fakeUpdateCondition(false);
+        mCondition2.fakeUpdateCondition(false);
+        mCondition3.fakeUpdateCondition(false);
+
+        final Monitor.Callback callback =
+                mock(Monitor.Callback.class);
+
+        mConditionMonitor.addSubscription(getDefaultBuilder(callback)
+                .addCondition(overridingCondition)
+                .build());
+
+        mExecutor.runAllReady();
+        verify(callback).onConditionsChanged(false);
+        clearInvocations(callback);
+
+        overridingCondition.fakeUpdateCondition(true);
+        mExecutor.runAllReady();
+        verify(callback).onConditionsChanged(true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
index 9e0f863..0b53133 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
@@ -133,4 +133,12 @@
         mCondition.fakeUpdateCondition(false);
         verify(callback, never()).onConditionChanged(eq(mCondition));
     }
+
+    @Test
+    public void clearCondition_reportsNotSet() {
+        mCondition.fakeUpdateCondition(false);
+        assertThat(mCondition.isConditionSet()).isTrue();
+        mCondition.clearCondition();
+        assertThat(mCondition.isConditionSet()).isFalse();
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index c56fdb1..5d52be2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.flags
 
+import java.io.PrintWriter
+
 class FakeFeatureFlags : FeatureFlags {
     private val booleanFlags = mutableMapOf<Int, Boolean>()
     private val stringFlags = mutableMapOf<Int, String>()
@@ -106,6 +108,10 @@
         }
     }
 
+    override fun dump(writer: PrintWriter, args: Array<out String>?) {
+        // no-op
+    }
+
     private fun flagName(flagId: Int): String {
         return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)"
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
index 9d5ccbe..1353ad2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
@@ -21,6 +21,14 @@
  * condition fulfillment.
  */
 public class FakeCondition extends Condition {
+    FakeCondition() {
+        super();
+    }
+
+    FakeCondition(Boolean initialValue, Boolean overriding) {
+        super(initialValue, overriding);
+    }
+
     @Override
     public void start() {}
 
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 4e4f454..b8af1bf 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -118,7 +118,7 @@
 
     public void startDream(Binder token, ComponentName name,
             boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock,
-            ComponentName overlayComponentName) {
+            ComponentName overlayComponentName, String reason) {
         stopDream(true /*immediate*/, "starting new dream");
 
         Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream");
@@ -128,7 +128,7 @@
 
             Slog.i(TAG, "Starting dream: name=" + name
                     + ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze
-                    + ", userId=" + userId);
+                    + ", userId=" + userId + ", reason='" + reason + "'");
 
             mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock);
 
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index e1b18f2..2f18e78 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -59,6 +59,7 @@
 import android.view.Display;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.internal.util.DumpUtils;
@@ -84,6 +85,9 @@
     private static final boolean DEBUG = false;
     private static final String TAG = "DreamManagerService";
 
+    private static final String DOZE_WAKE_LOCK_TAG = "dream:doze";
+    private static final String DREAM_WAKE_LOCK_TAG = "dream:dream";
+
     private final Object mLock = new Object();
 
     private final Context mContext;
@@ -98,17 +102,11 @@
     private final ComponentName mAmbientDisplayComponent;
     private final boolean mDismissDreamOnActivityStart;
 
-    private Binder mCurrentDreamToken;
-    private ComponentName mCurrentDreamName;
-    private int mCurrentDreamUserId;
-    private boolean mCurrentDreamIsPreview;
-    private boolean mCurrentDreamCanDoze;
-    private boolean mCurrentDreamIsDozing;
-    private boolean mCurrentDreamIsWaking;
+    @GuardedBy("mLock")
+    private DreamRecord mCurrentDream;
+
     private boolean mForceAmbientDisplayEnabled;
-    private boolean mDreamsOnlyEnabledForSystemUser;
-    private int mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN;
-    private int mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
+    private final boolean mDreamsOnlyEnabledForSystemUser;
 
     // A temporary dream component that, when present, takes precedence over user configured dream
     // component.
@@ -116,7 +114,7 @@
 
     private ComponentName mDreamOverlayServiceName;
 
-    private AmbientDisplayConfiguration mDozeConfig;
+    private final AmbientDisplayConfiguration mDozeConfig;
     private final ActivityInterceptorCallback mActivityInterceptorCallback =
             new ActivityInterceptorCallback() {
                 @Nullable
@@ -132,8 +130,14 @@
                     final boolean activityAllowed = activityType == ACTIVITY_TYPE_HOME
                             || activityType == ACTIVITY_TYPE_DREAM
                             || activityType == ACTIVITY_TYPE_ASSISTANT;
-                    if (mCurrentDreamToken != null && !mCurrentDreamIsWaking
-                            && !mCurrentDreamIsDozing && !activityAllowed) {
+
+                    boolean shouldRequestAwaken;
+                    synchronized (mLock) {
+                        shouldRequestAwaken = mCurrentDream != null && !mCurrentDream.isWaking
+                                && !mCurrentDream.isDozing && !activityAllowed;
+                    }
+
+                    if (shouldRequestAwaken) {
                         requestAwakenInternal(
                                 "stopping dream due to activity start: " + activityInfo.name);
                     }
@@ -149,7 +153,7 @@
         mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mPowerManagerInternal = getLocalService(PowerManagerInternal.class);
         mAtmInternal = getLocalService(ActivityTaskManagerInternal.class);
-        mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, TAG);
+        mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, DOZE_WAKE_LOCK_TAG);
         mDozeConfig = new AmbientDisplayConfiguration(mContext);
         mUiEventLogger = new UiEventLoggerImpl();
         mDreamUiEventLogger = new DreamUiEventLoggerImpl(
@@ -197,43 +201,38 @@
     }
 
     private void dumpInternal(PrintWriter pw) {
-        pw.println("DREAM MANAGER (dumpsys dreams)");
-        pw.println();
-        pw.println("mCurrentDreamToken=" + mCurrentDreamToken);
-        pw.println("mCurrentDreamName=" + mCurrentDreamName);
-        pw.println("mCurrentDreamUserId=" + mCurrentDreamUserId);
-        pw.println("mCurrentDreamIsPreview=" + mCurrentDreamIsPreview);
-        pw.println("mCurrentDreamCanDoze=" + mCurrentDreamCanDoze);
-        pw.println("mCurrentDreamIsDozing=" + mCurrentDreamIsDozing);
-        pw.println("mCurrentDreamIsWaking=" + mCurrentDreamIsWaking);
-        pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
-        pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
-        pw.println("mCurrentDreamDozeScreenState="
-                + Display.stateToString(mCurrentDreamDozeScreenState));
-        pw.println("mCurrentDreamDozeScreenBrightness=" + mCurrentDreamDozeScreenBrightness);
-        pw.println("getDozeComponent()=" + getDozeComponent());
-        pw.println();
+        synchronized (mLock) {
+            pw.println("DREAM MANAGER (dumpsys dreams)");
+            pw.println();
+            pw.println("mCurrentDream=" + mCurrentDream);
+            pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
+            pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
+            pw.println("getDozeComponent()=" + getDozeComponent());
+            pw.println();
 
-        DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() {
-            @Override
-            public void dump(PrintWriter pw, String prefix) {
-                mController.dump(pw);
-            }
-        }, pw, "", 200);
+            DumpUtils.dumpAsync(mHandler, (pw1, prefix) -> mController.dump(pw1), pw, "", 200);
+        }
     }
 
     /** Whether a real dream is occurring. */
     private boolean isDreamingInternal() {
         synchronized (mLock) {
-            return mCurrentDreamToken != null && !mCurrentDreamIsPreview
-                    && !mCurrentDreamIsWaking;
+            return mCurrentDream != null && !mCurrentDream.isPreview
+                    && !mCurrentDream.isWaking;
+        }
+    }
+
+    /** Whether a doze is occurring. */
+    private boolean isDozingInternal() {
+        synchronized (mLock) {
+            return mCurrentDream != null && mCurrentDream.isDozing;
         }
     }
 
     /** Whether a real dream, or a dream preview is occurring. */
     private boolean isDreamingOrInPreviewInternal() {
         synchronized (mLock) {
-            return mCurrentDreamToken != null && !mCurrentDreamIsWaking;
+            return mCurrentDream != null && !mCurrentDream.isWaking;
         }
     }
 
@@ -273,7 +272,7 @@
         // locks are held and the user activity timeout has expired then the
         // device may simply go to sleep.
         synchronized (mLock) {
-            if (mCurrentDreamToken == token) {
+            if (mCurrentDream != null && mCurrentDream.token == token) {
                 stopDreamLocked(immediate, "finished self");
             }
         }
@@ -281,16 +280,17 @@
 
     private void testDreamInternal(ComponentName dream, int userId) {
         synchronized (mLock) {
-            startDreamLocked(dream, true /*isPreviewMode*/, false /*canDoze*/, userId);
+            startDreamLocked(dream, true /*isPreviewMode*/, false /*canDoze*/, userId,
+                    "test dream" /*reason*/);
         }
     }
 
-    private void startDreamInternal(boolean doze) {
+    private void startDreamInternal(boolean doze, String reason) {
         final int userId = ActivityManager.getCurrentUser();
         final ComponentName dream = chooseDreamForUser(doze, userId);
         if (dream != null) {
             synchronized (mLock) {
-                startDreamLocked(dream, false /*isPreviewMode*/, doze, userId);
+                startDreamLocked(dream, false /*isPreviewMode*/, doze, userId, reason);
             }
         }
     }
@@ -314,13 +314,13 @@
         }
 
         synchronized (mLock) {
-            if (mCurrentDreamToken == token && mCurrentDreamCanDoze) {
-                mCurrentDreamDozeScreenState = screenState;
-                mCurrentDreamDozeScreenBrightness = screenBrightness;
+            if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.canDoze) {
+                mCurrentDream.dozeScreenState = screenState;
+                mCurrentDream.dozeScreenBrightness = screenBrightness;
                 mPowerManagerInternal.setDozeOverrideFromDreamManager(
                         screenState, screenBrightness);
-                if (!mCurrentDreamIsDozing) {
-                    mCurrentDreamIsDozing = true;
+                if (!mCurrentDream.isDozing) {
+                    mCurrentDream.isDozing = true;
                     mDozeWakeLock.acquire();
                 }
             }
@@ -333,8 +333,8 @@
         }
 
         synchronized (mLock) {
-            if (mCurrentDreamToken == token && mCurrentDreamIsDozing) {
-                mCurrentDreamIsDozing = false;
+            if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.isDozing) {
+                mCurrentDream.isDozing = false;
                 mDozeWakeLock.release();
                 mPowerManagerInternal.setDozeOverrideFromDreamManager(
                         Display.STATE_UNKNOWN, PowerManager.BRIGHTNESS_DEFAULT);
@@ -403,7 +403,7 @@
         ComponentName[] components = componentsFromString(names);
 
         // first, ensure components point to valid services
-        List<ComponentName> validComponents = new ArrayList<ComponentName>();
+        List<ComponentName> validComponents = new ArrayList<>();
         if (components != null) {
             for (ComponentName component : components) {
                 if (validateDream(component)) {
@@ -439,8 +439,9 @@
             mSystemDreamComponent = componentName;
 
             // Switch dream if currently dreaming and not dozing.
-            if (isDreamingInternal() && !mCurrentDreamIsDozing) {
-                startDreamInternal(false);
+            if (isDreamingInternal() && !isDozingInternal()) {
+                startDreamInternal(false /*doze*/, (mSystemDreamComponent == null ? "clear" : "set")
+                        + " system dream component" /*reason*/);
             }
         }
     }
@@ -478,13 +479,16 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void startDreamLocked(final ComponentName name,
-            final boolean isPreviewMode, final boolean canDoze, final int userId) {
-        if (!mCurrentDreamIsWaking
-                && Objects.equals(mCurrentDreamName, name)
-                && mCurrentDreamIsPreview == isPreviewMode
-                && mCurrentDreamCanDoze == canDoze
-                && mCurrentDreamUserId == userId) {
+            final boolean isPreviewMode, final boolean canDoze, final int userId,
+            final String reason) {
+        if (mCurrentDream != null
+                && !mCurrentDream.isWaking
+                && Objects.equals(mCurrentDream.name, name)
+                && mCurrentDream.isPreview == isPreviewMode
+                && mCurrentDream.canDoze == canDoze
+                && mCurrentDream.userId == userId) {
             Slog.i(TAG, "Already in target dream.");
             return;
         }
@@ -493,73 +497,60 @@
 
         Slog.i(TAG, "Entering dreamland.");
 
-        final Binder newToken = new Binder();
-        mCurrentDreamToken = newToken;
-        mCurrentDreamName = name;
-        mCurrentDreamIsPreview = isPreviewMode;
-        mCurrentDreamCanDoze = canDoze;
-        mCurrentDreamUserId = userId;
+        mCurrentDream = new DreamRecord(name, userId, isPreviewMode, canDoze);
 
-        if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
+        if (!mCurrentDream.name.equals(mAmbientDisplayComponent)) {
             // TODO(b/213906448): Remove when metrics based on new atom are fully rolled out.
             mUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_START);
             mDreamUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_START,
-                    mCurrentDreamName.flattenToString());
+                    mCurrentDream.name.flattenToString());
         }
 
         PowerManager.WakeLock wakeLock = mPowerManager
-                .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream");
+                .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, DREAM_WAKE_LOCK_TAG);
+        final Binder dreamToken = mCurrentDream.token;
         mHandler.post(wakeLock.wrap(() -> {
             mAtmInternal.notifyDreamStateChanged(true);
-            mController.startDream(newToken, name, isPreviewMode, canDoze, userId, wakeLock,
-                    mDreamOverlayServiceName);
+            mController.startDream(dreamToken, name, isPreviewMode, canDoze, userId, wakeLock,
+                    mDreamOverlayServiceName, reason);
         }));
     }
 
+    @GuardedBy("mLock")
     private void stopDreamLocked(final boolean immediate, String reason) {
-        if (mCurrentDreamToken != null) {
+        if (mCurrentDream != null) {
             if (immediate) {
                 Slog.i(TAG, "Leaving dreamland.");
                 cleanupDreamLocked();
-            } else if (mCurrentDreamIsWaking) {
+            } else if (mCurrentDream.isWaking) {
                 return; // already waking
             } else {
                 Slog.i(TAG, "Gently waking up from dream.");
-                mCurrentDreamIsWaking = true;
+                mCurrentDream.isWaking = true;
             }
 
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    Slog.i(TAG, "Performing gentle wake from dream.");
-                    mController.stopDream(immediate, reason);
-                }
-            });
+            mHandler.post(() -> mController.stopDream(immediate, reason));
         }
     }
 
+    @GuardedBy("mLock")
     private void cleanupDreamLocked() {
-        if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
+        mHandler.post(() -> mAtmInternal.notifyDreamStateChanged(false /*dreaming*/));
+
+        if (mCurrentDream == null) {
+            return;
+        }
+
+        if (!mCurrentDream.name.equals(mAmbientDisplayComponent)) {
             // TODO(b/213906448): Remove when metrics based on new atom are fully rolled out.
             mUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_STOP);
             mDreamUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_STOP,
-                    mCurrentDreamName.flattenToString());
+                    mCurrentDream.name.flattenToString());
         }
-        mCurrentDreamToken = null;
-        mCurrentDreamName = null;
-        mCurrentDreamIsPreview = false;
-        mCurrentDreamCanDoze = false;
-        mCurrentDreamUserId = 0;
-        mCurrentDreamIsWaking = false;
-        if (mCurrentDreamIsDozing) {
-            mCurrentDreamIsDozing = false;
+        if (mCurrentDream.isDozing) {
             mDozeWakeLock.release();
         }
-        mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN;
-        mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
-        mHandler.post(() -> {
-            mAtmInternal.notifyDreamStateChanged(false);
-        });
+        mCurrentDream = null;
     }
 
     private void checkPermission(String permission) {
@@ -606,7 +597,7 @@
         @Override
         public void onDreamStopped(Binder token) {
             synchronized (mLock) {
-                if (mCurrentDreamToken == token) {
+                if (mCurrentDream != null && mCurrentDream.token == token) {
                     cleanupDreamLocked();
                 }
             }
@@ -624,7 +615,7 @@
      * Handler for asynchronous operations performed by the dream manager.
      * Ensures operations to {@link DreamController} are single-threaded.
      */
-    private final class DreamHandler extends Handler {
+    private static final class DreamHandler extends Handler {
         public DreamHandler(Looper looper) {
             super(looper, null, true /*async*/);
         }
@@ -865,13 +856,13 @@
 
     private final class LocalService extends DreamManagerInternal {
         @Override
-        public void startDream(boolean doze) {
-            startDreamInternal(doze);
+        public void startDream(boolean doze, String reason) {
+            startDreamInternal(doze, reason);
         }
 
         @Override
-        public void stopDream(boolean immediate) {
-            stopDreamInternal(immediate, "requested stopDream");
+        public void stopDream(boolean immediate, String reason) {
+            stopDreamInternal(immediate, reason);
         }
 
         @Override
@@ -890,13 +881,47 @@
         }
     }
 
+    private static final class DreamRecord {
+        public final Binder token = new Binder();
+        public final ComponentName name;
+        public final int userId;
+        public final boolean isPreview;
+        public final boolean canDoze;
+        public boolean isDozing = false;
+        public boolean isWaking = false;
+        public int dozeScreenState = Display.STATE_UNKNOWN;
+        public int dozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
+
+        DreamRecord(ComponentName name, int userId, boolean isPreview, boolean canDoze) {
+            this.name = name;
+            this.userId = userId;
+            this.isPreview = isPreview;
+            this.canDoze = canDoze;
+        }
+
+        @Override
+        public String toString() {
+            return "DreamRecord{"
+                    + "token=" + token
+                    + ", name=" + name
+                    + ", userId=" + userId
+                    + ", isPreview=" + isPreview
+                    + ", canDoze=" + canDoze
+                    + ", isDozing=" + isDozing
+                    + ", isWaking=" + isWaking
+                    + ", dozeScreenState=" + dozeScreenState
+                    + ", dozeScreenBrightness=" + dozeScreenBrightness
+                    + '}';
+        }
+    }
+
     private final Runnable mSystemPropertiesChanged = new Runnable() {
         @Override
         public void run() {
             if (DEBUG) Slog.d(TAG, "System properties changed");
             synchronized (mLock) {
-                if (mCurrentDreamName != null && mCurrentDreamCanDoze
-                        && !mCurrentDreamName.equals(getDozeComponent())) {
+                if (mCurrentDream != null &&  mCurrentDream.name != null && mCurrentDream.canDoze
+                        && !mCurrentDream.name.equals(getDozeComponent())) {
                     // May have updated the doze component, wake up
                     mPowerManager.wakeUp(SystemClock.uptimeMillis(),
                             "android.server.dreams:SYSPROP");
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 00974e4..2be84d0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2036,9 +2036,11 @@
             if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
+            final int callingUid = Binder.getCallingUid();
             final long ident = Binder.clearCallingIdentity();
             try {
-                return getInputMethodListLocked(resolvedUserIds[0], directBootAwareness);
+                return getInputMethodListLocked(
+                        resolvedUserIds[0], directBootAwareness, callingUid);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2057,9 +2059,10 @@
             if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
+            final int callingUid = Binder.getCallingUid();
             final long ident = Binder.clearCallingIdentity();
             try {
-                return getEnabledInputMethodListLocked(resolvedUserIds[0]);
+                return getEnabledInputMethodListLocked(resolvedUserIds[0], callingUid);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2090,12 +2093,14 @@
 
     @GuardedBy("ImfLock.class")
     private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
-            @DirectBootAwareness int directBootAwareness) {
+            @DirectBootAwareness int directBootAwareness, int callingUid) {
         final ArrayList<InputMethodInfo> methodList;
+        final InputMethodSettings settings;
         if (userId == mSettings.getCurrentUserId()
                 && directBootAwareness == DirectBootAwareness.AUTO) {
             // Create a copy.
             methodList = new ArrayList<>(mMethodList);
+            settings = mSettings;
         } else {
             final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
             methodList = new ArrayList<>();
@@ -2104,19 +2109,31 @@
             AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
             queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
                     methodList, directBootAwareness);
+            settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */);
         }
+        // filter caller's access to input methods
+        methodList.removeIf(imi ->
+                !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
         return methodList;
     }
 
     @GuardedBy("ImfLock.class")
-    private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId) {
+    private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId,
+            int callingUid) {
+        final ArrayList<InputMethodInfo> methodList;
+        final InputMethodSettings settings;
         if (userId == mSettings.getCurrentUserId()) {
-            return mSettings.getEnabledInputMethodListLocked();
+            methodList = mSettings.getEnabledInputMethodListLocked();
+            settings = mSettings;
+        } else {
+            final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
+            settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */);
+            methodList = settings.getEnabledInputMethodListLocked();
         }
-        final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
-        final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId,
-                true);
-        return settings.getEnabledInputMethodListLocked();
+        // filter caller's access to input methods
+        methodList.removeIf(imi ->
+                !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
+        return methodList;
     }
 
     @GuardedBy("ImfLock.class")
@@ -2155,10 +2172,11 @@
         }
 
         synchronized (ImfLock.class) {
+            final int callingUid = Binder.getCallingUid();
             final long ident = Binder.clearCallingIdentity();
             try {
                 return getEnabledInputMethodSubtypeListLocked(imiId,
-                        allowsImplicitlyEnabledSubtypes, userId);
+                        allowsImplicitlyEnabledSubtypes, userId, callingUid);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2167,7 +2185,7 @@
 
     @GuardedBy("ImfLock.class")
     private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
-            boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
+            boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
         if (userId == mSettings.getCurrentUserId()) {
             final InputMethodInfo imi;
             String selectedMethodId = getSelectedMethodIdLocked();
@@ -2176,7 +2194,8 @@
             } else {
                 imi = mMethodMap.get(imiId);
             }
-            if (imi == null) {
+            if (imi == null || !canCallerAccessInputMethod(
+                    imi.getPackageName(), callingUid, userId, mSettings)) {
                 return Collections.emptyList();
             }
             return mSettings.getEnabledInputMethodSubtypeListLocked(
@@ -2189,6 +2208,9 @@
         }
         final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId,
                 true);
+        if (!canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)) {
+            return Collections.emptyList();
+        }
         return settings.getEnabledInputMethodSubtypeListLocked(
                 imi, allowsImplicitlyEnabledSubtypes);
     }
@@ -5438,6 +5460,34 @@
         return true;
     }
 
+    /**
+     * Filter the access to the input method by rules of the package visibility. Return {@code true}
+     * if the given input method is the currently selected one or visible to the caller.
+     *
+     * @param targetPkgName The package name of input method to check.
+     * @param callingUid The caller that is going to access the input method.
+     * @param userId The user ID where the input method resides.
+     * @param settings The input method settings under the given user ID.
+     * @return {@code true} if caller is able to access the input method.
+     */
+    private boolean canCallerAccessInputMethod(@NonNull String targetPkgName, int callingUid,
+            @UserIdInt int userId, @NonNull InputMethodSettings settings) {
+        final String methodId = settings.getSelectedInputMethod();
+        final ComponentName selectedInputMethod = methodId != null
+                ? InputMethodUtils.convertIdToComponentName(methodId) : null;
+        if (selectedInputMethod != null
+                && selectedInputMethod.getPackageName().equals(targetPkgName)) {
+            return true;
+        }
+        final boolean canAccess = !mPackageManagerInternal.filterAppAccess(
+                targetPkgName, callingUid, userId);
+        if (DEBUG && !canAccess) {
+            Slog.d(TAG, "Input method " + targetPkgName
+                    + " is not visible to the caller " + callingUid);
+        }
+        return canAccess;
+    }
+
     private void publishLocalService() {
         LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl());
     }
@@ -5459,14 +5509,15 @@
         @Override
         public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
             synchronized (ImfLock.class) {
-                return getInputMethodListLocked(userId, DirectBootAwareness.AUTO);
+                return getInputMethodListLocked(userId, DirectBootAwareness.AUTO,
+                        Process.SYSTEM_UID);
             }
         }
 
         @Override
         public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
             synchronized (ImfLock.class) {
-                return getEnabledInputMethodListLocked(userId);
+                return getEnabledInputMethodListLocked(userId, Process.SYSTEM_UID);
             }
         }
 
@@ -6096,8 +6147,9 @@
             try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
                 for (int userId : userIds) {
                     final List<InputMethodInfo> methods = all
-                            ? getInputMethodListLocked(userId, DirectBootAwareness.AUTO)
-                            : getEnabledInputMethodListLocked(userId);
+                            ? getInputMethodListLocked(
+                                    userId, DirectBootAwareness.AUTO, Process.SHELL_UID)
+                            : getEnabledInputMethodListLocked(userId, Process.SHELL_UID);
                     if (userIds.length > 1) {
                         pr.print("User #");
                         pr.print(userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 69b0661..c7ff8ca 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -20,11 +20,13 @@
 import android.annotation.Nullable;
 import android.annotation.UserHandleAware;
 import android.annotation.UserIdInt;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.Build;
 import android.os.UserHandle;
@@ -645,13 +647,11 @@
                         if (imi != null && imi.getSubtypeCount() > 0) {
                             List<InputMethodSubtype> implicitlyEnabledSubtypes =
                                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(mRes, imi);
-                            if (implicitlyEnabledSubtypes != null) {
-                                final int numSubtypes = implicitlyEnabledSubtypes.size();
-                                for (int i = 0; i < numSubtypes; ++i) {
-                                    final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i);
-                                    if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
-                                        return subtypeHashCode;
-                                    }
+                            final int numSubtypes = implicitlyEnabledSubtypes.size();
+                            for (int i = 0; i < numSubtypes; ++i) {
+                                final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i);
+                                if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
+                                    return subtypeHashCode;
                                 }
                             }
                         }
@@ -1000,4 +1000,16 @@
         }
         return new int[]{sourceUserId};
     }
+
+    /**
+     * Convert the input method ID to a component name
+     *
+     * @param id A unique ID for this input method.
+     * @return The component name of the input method.
+     * @see InputMethodInfo#computeId(ResolveInfo)
+     */
+    @Nullable
+    public static ComponentName convertIdToComponentName(@NonNull String id) {
+        return ComponentName.unflattenFromString(id);
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
index f07539f..f49fa6e 100644
--- a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.server.inputmethod;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.os.LocaleList;
@@ -123,6 +124,7 @@
             source -> source != null ? source.getLocaleObject() : null;
 
     @VisibleForTesting
+    @NonNull
     static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
             Resources res, InputMethodInfo imi) {
         final LocaleList systemLocales = res.getConfiguration().getLocales();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 29341c7..ae99806 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1592,7 +1592,7 @@
         // If there's a dream running then use home to escape the dream
         // but don't actually go home.
         if (mDreamManagerInternal != null && mDreamManagerInternal.isDreaming()) {
-            mDreamManagerInternal.stopDream(false /*immediate*/);
+            mDreamManagerInternal.stopDream(false /*immediate*/, "short press on home" /*reason*/);
             return;
         }
 
@@ -4194,6 +4194,8 @@
         mCameraGestureTriggered = true;
         if (mRequestedOrSleepingDefaultDisplay) {
             mCameraGestureTriggeredDuringGoingToSleep = true;
+            // Wake device up early to prevent display doing redundant turning off/on stuff.
+            wakeUpFromPowerKey(event.getDownTime());
         }
         return true;
     }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index f9352cb..ef787dc 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -3278,8 +3278,10 @@
         if (mDreamManager != null) {
             // Restart the dream whenever the sandman is summoned.
             if (startDreaming) {
-                mDreamManager.stopDream(/* immediate= */ false);
-                mDreamManager.startDream(wakefulness == WAKEFULNESS_DOZING);
+                mDreamManager.stopDream(/* immediate= */ false,
+                        "power manager request before starting dream" /*reason*/);
+                mDreamManager.startDream(wakefulness == WAKEFULNESS_DOZING,
+                        "power manager request" /*reason*/);
             }
             isDreaming = mDreamManager.isDreaming();
         } else {
@@ -3364,7 +3366,7 @@
 
         // Stop dream.
         if (isDreaming) {
-            mDreamManager.stopDream(/* immediate= */ false);
+            mDreamManager.stopDream(/* immediate= */ false, "power manager request" /*reason*/);
         }
     }
 
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 8510bd3..1e5b498 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -1331,6 +1331,13 @@
     LongSamplingCounter mMobileRadioActiveUnknownTime;
     LongSamplingCounter mMobileRadioActiveUnknownCount;
 
+    /**
+     * The soonest the Mobile Radio stats can be updated due to a mobile radio power state change
+     * after it was last updated.
+     */
+    @VisibleForTesting
+    protected static final long MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS = 1000 * 60 * 10;
+
     int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
 
     @GuardedBy("this")
@@ -5531,6 +5538,15 @@
             } else {
                 mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs);
                 mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs);
+
+                if (mLastModemActivityInfo != null) {
+                    if (elapsedRealtimeMs < mLastModemActivityInfo.getTimestampMillis()
+                            + MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS) {
+                        // Modem Activity info has been collected recently, don't bother
+                        // triggering another update.
+                        return false;
+                    }
+                }
                 // Tell the caller to collect radio network/power stats.
                 return true;
             }
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 f6852d8..e0086d0 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -800,7 +800,7 @@
         doAnswer(inv -> {
             when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
             return null;
-        }).when(mDreamManagerInternalMock).startDream(anyBoolean());
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
 
         setMinimumScreenOffTimeoutConfig(5);
         createService();
@@ -822,7 +822,7 @@
         doAnswer(inv -> {
             when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
             return null;
-        }).when(mDreamManagerInternalMock).startDream(anyBoolean());
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
 
         setMinimumScreenOffTimeoutConfig(5);
         createService();
@@ -1271,7 +1271,7 @@
         doAnswer(inv -> {
             when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
             return null;
-        }).when(mDreamManagerInternalMock).startDream(anyBoolean());
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
 
         final String pkg = mContextSpy.getOpPackageName();
         final Binder token = new Binder();
@@ -1767,7 +1767,7 @@
         forceDozing();
         // Allow handleSandman() to be called asynchronously
         advanceTime(500);
-        verify(mDreamManagerInternalMock).startDream(eq(true));
+        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
     }
 
     @Test
@@ -1805,7 +1805,7 @@
 
         // Allow handleSandman() to be called asynchronously
         advanceTime(500);
-        verify(mDreamManagerInternalMock).startDream(eq(true));
+        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index 0a1e3c7..e9c5b01 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -47,6 +47,7 @@
 import android.telephony.ModemActivityInfo;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.util.Log;
 import android.util.MutableInt;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
@@ -83,6 +84,7 @@
  *      com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
  */
 public class BatteryStatsNoteTest extends TestCase {
+    private static final String TAG = BatteryStatsNoteTest.class.getSimpleName();
 
     private static final int UID = 10500;
     private static final int ISOLATED_APP_ID = Process.FIRST_ISOLATED_UID + 23;
@@ -2030,6 +2032,113 @@
                 noRadioProcFlags, lastProcStateChangeFlags.value);
     }
 
+    @SmallTest
+    public void testNoteMobileRadioPowerStateLocked() {
+        long curr;
+        boolean update;
+        final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setOnBatteryInternal(true);
+
+        // Note mobile radio is on.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 1001);
+        bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+                UID);
+
+        // Note mobile radio is still on.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 2001);
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                curr, UID);
+        assertFalse(
+                "noteMobileRadioPowerStateLocked should not request an update when the power "
+                        + "state does not change from HIGH.",
+                update);
+
+        // Note mobile radio is off.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 3001);
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                curr, UID);
+        assertTrue(
+                "noteMobileRadioPowerStateLocked should request an update when the power state "
+                        + "changes from HIGH to LOW.",
+                update);
+
+        // Note mobile radio is still off.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 4001);
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                curr, UID);
+        assertFalse(
+                "noteMobileRadioPowerStateLocked should not request an update when the power "
+                        + "state does not change from LOW.",
+                update);
+
+        // Note mobile radio is on.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 5001);
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                curr, UID);
+        assertFalse(
+                "noteMobileRadioPowerStateLocked should not request an update when the power "
+                        + "state changes from LOW to HIGH.",
+                update);
+    }
+
+    @SmallTest
+    public void testNoteMobileRadioPowerStateLocked_rateLimited() {
+        long curr;
+        boolean update;
+        final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setPowerProfile(mock(PowerProfile.class));
+
+        final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels();
+        final ModemActivityInfo mai = new ModemActivityInfo(0L, 0L, 0L, new int[txLevelCount], 0L);
+
+        final long rateLimit = bi.getMobileRadioPowerStateUpdateRateLimit();
+        if (rateLimit < 0) {
+            Log.w(TAG, "Skipping testNoteMobileRadioPowerStateLocked_rateLimited, rateLimit = "
+                    + rateLimit);
+            return;
+        }
+        bi.setOnBatteryInternal(true);
+
+        // Note mobile radio is on.
+        curr = 1000L * (clocks.realtime = clocks.uptime = 1001);
+        bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+                UID);
+
+        clocks.realtime = clocks.uptime = 2001;
+        mai.setTimestamp(clocks.realtime);
+        bi.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE,
+                clocks.realtime, clocks.uptime, mNetworkStatsManager);
+
+        // Note mobile radio is off within the rate limit duration.
+        clocks.realtime = clocks.uptime = clocks.realtime + (long) (rateLimit * 0.7);
+        curr = 1000L * clocks.realtime;
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                curr, UID);
+        assertFalse(
+                "noteMobileRadioPowerStateLocked should not request an update when the power "
+                        + "state so soon after a noteModemControllerActivity",
+                update);
+
+        // Note mobile radio is on.
+        clocks.realtime = clocks.uptime = clocks.realtime + (long) (rateLimit * 0.7);
+        curr = 1000L * clocks.realtime;
+        bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+                UID);
+
+        // Note mobile radio is off much later
+        clocks.realtime = clocks.uptime = clocks.realtime + rateLimit;
+        curr = 1000L * clocks.realtime;
+        update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+                curr, UID);
+        assertTrue(
+                "noteMobileRadioPowerStateLocked should request an update when the power state "
+                        + "changes from HIGH to LOW much later after a "
+                        + "noteModemControllerActivity.",
+                update);
+    }
+
     private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
         // Note that noteUidProcessStateLocked uses ActivityManager process states.
         if (fgOn) {
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 570b2ee..df4b896 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -113,6 +113,10 @@
         return getUidStatsLocked(uid).mOnBatteryScreenOffBackgroundTimeBase;
     }
 
+    public long getMobileRadioPowerStateUpdateRateLimit() {
+        return MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS;
+    }
+
     public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) {
         mNetworkStats = networkStats;
         return this;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 2a1efed..0622612 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3348,27 +3348,42 @@
     public static final String KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_CLASS_OVERRIDE_STRING =
             "carrier_qualified_networks_service_class_override_string";
     /**
-     * A list of 4 LTE RSCP thresholds above which a signal level is considered POOR,
+     * A list of 4 WCDMA RSCP thresholds above which a signal level is considered POOR,
      * MODERATE, GOOD, or EXCELLENT, to be used in SignalStrength reporting.
      *
      * Note that the min and max thresholds are fixed at -120 and -24, as set in 3GPP TS 27.007
      * section 8.69.
      * <p>
-     * See SignalStrength#MAX_WCDMA_RSCP and SignalStrength#MIN_WDCMA_RSCP. Any signal level outside
-     * these boundaries is considered invalid.
+     * See CellSignalStrengthWcdma#WCDMA_RSCP_MAX and CellSignalStrengthWcdma#WCDMA_RSCP_MIN.
+     * Any signal level outside these boundaries is considered invalid.
      * @hide
      */
     public static final String KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY =
             "wcdma_rscp_thresholds_int_array";
 
     /**
+     * A list of 4 WCDMA ECNO thresholds above which a signal level is considered POOR,
+     * MODERATE, GOOD, or EXCELLENT, to be used in SignalStrength reporting.
+     *
+     * Note that the min and max thresholds are fixed at -24 and 1, as set in 3GPP TS 25.215
+     * section 5.1.5.
+     * Any signal level outside these boundaries is considered invalid.
+     * <p>
+     *
+     * The default value is {@code {-24, -14, -6, 1}}.
+     * @hide
+     */
+    public static final String KEY_WCDMA_ECNO_THRESHOLDS_INT_ARRAY =
+            "wcdma_ecno_thresholds_int_array";
+
+    /**
      * The default measurement to use for signal strength reporting. If this is not specified, the
      * RSSI is used.
      * <p>
      * e.g.) To use RSCP by default, set the value to "rscp". The signal strength level will
      * then be determined by #KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY
      * <p>
-     * Currently this supports the value "rscp" and "rssi".
+     * Currently this supports the value "rscp","rssi" and "ecno".
      * @hide
      */
     // FIXME: this key and related keys must not be exposed without a consistent philosophy for
@@ -9142,6 +9157,7 @@
         sDefaults.putBoolean(KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL, false);
         sDefaults.putBoolean(KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL, false);
         sDefaults.putIntArray(KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                // Boundaries: [-140 dBm, -44 dBm]
                 new int[] {
                         -128, /* SIGNAL_STRENGTH_POOR */
                         -118, /* SIGNAL_STRENGTH_MODERATE */
@@ -9149,6 +9165,7 @@
                         -98,  /* SIGNAL_STRENGTH_GREAT */
                 });
         sDefaults.putIntArray(KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY,
+                // Boundaries: [-34 dB, 3 dB]
                 new int[] {
                         -20, /* SIGNAL_STRENGTH_POOR */
                         -17, /* SIGNAL_STRENGTH_MODERATE */
@@ -9156,6 +9173,7 @@
                         -11  /* SIGNAL_STRENGTH_GREAT */
                 });
         sDefaults.putIntArray(KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY,
+                // Boundaries: [-20 dBm, 30 dBm]
                 new int[] {
                         -3, /* SIGNAL_STRENGTH_POOR */
                         1,  /* SIGNAL_STRENGTH_MODERATE */
@@ -9163,12 +9181,23 @@
                         13  /* SIGNAL_STRENGTH_GREAT */
                 });
         sDefaults.putIntArray(KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY,
+                // Boundaries: [-120 dBm, -25 dBm]
                 new int[] {
                         -115,  /* SIGNAL_STRENGTH_POOR */
                         -105, /* SIGNAL_STRENGTH_MODERATE */
                         -95, /* SIGNAL_STRENGTH_GOOD */
                         -85  /* SIGNAL_STRENGTH_GREAT */
                 });
+        // TODO(b/249896055): On enabling ECNO measurement part for Signal Bar level indication
+        // system functionality,below values to be rechecked.
+        sDefaults.putIntArray(KEY_WCDMA_ECNO_THRESHOLDS_INT_ARRAY,
+                // Boundaries: [-24 dBm, 1 dBm]
+                new int[] {
+                        -24, /* SIGNAL_STRENGTH_POOR */
+                        -14, /* SIGNAL_STRENGTH_MODERATE */
+                        -6, /* SIGNAL_STRENGTH_GOOD */
+                        1  /* SIGNAL_STRENGTH_GREAT */
+                });
         sDefaults.putIntArray(KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY,
                 // Boundaries: [-140 dB, -44 dB]
                 new int[] {
diff --git a/tests/TrustTests/Android.bp b/tests/TrustTests/Android.bp
index 77f98e8..a1b888a 100644
--- a/tests/TrustTests/Android.bp
+++ b/tests/TrustTests/Android.bp
@@ -24,7 +24,7 @@
     static_libs: [
         "androidx.test.rules",
         "androidx.test.ext.junit",
-        "androidx.test.uiautomator",
+        "androidx.test.uiautomator_uiautomator",
         "mockito-target-minus-junit4",
         "servicestests-utils",
         "truth-prebuilt",