Merge "Add VolumeDialogInteractor to capture the show/dismiss UI events of VolumeDialog." into main
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShimPriv_apk.asciipb
index e8e7ec9..e898091 100644
--- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShimPriv_apk.asciipb
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShimPriv_apk.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "11947186"
+    build_id: "9653376"
     target: "CtsShim"
     source_file: "aosp_riscv64/CtsShimPriv.apk"
   }
@@ -8,7 +8,7 @@
   version: ""
   version_group: ""
   git_project: "platform/frameworks/base"
-  git_branch: "main"
+  git_branch: "master"
   transform: TRANSFORM_NONE
   transform_options {
   }
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShim_apk.asciipb
index 6113b6a..04092366 100644
--- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShim_apk.asciipb
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__riscv64_CtsShim_apk.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "11947186"
+    build_id: "9653376"
     target: "CtsShim"
     source_file: "aosp_riscv64/CtsShim.apk"
   }
@@ -8,7 +8,7 @@
   version: ""
   version_group: ""
   git_project: "platform/frameworks/base"
-  git_branch: "main"
+  git_branch: "master"
   transform: TRANSFORM_NONE
   transform_options {
   }
diff --git a/Android.bp b/Android.bp
index 2becf07..fb1fa3b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -115,7 +115,7 @@
         ":android.security.legacykeystore-java-source",
         ":android.security.maintenance-java-source",
         ":android.security.metrics-java-source",
-        ":android.system.keystore2-V3-java-source",
+        ":android.system.keystore2-V4-java-source",
         ":android.hardware.cas-V1-java-source",
         ":credstore_aidl",
         ":dumpstate_aidl",
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index c4ffa34..fea2b7b 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -33,4 +33,4 @@
 flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PROJECT}
 
 [Tool Paths]
-ktfmt = ${REPO_ROOT}/prebuilts/build-tools/common/framework/ktfmt.jar
+ktfmt = ${REPO_ROOT}/external/ktfmt/ktfmt.sh
diff --git a/cmds/uiautomator/library/Android.bp b/cmds/uiautomator/library/Android.bp
index cffc078..cd1fb9a 100644
--- a/cmds/uiautomator/library/Android.bp
+++ b/cmds/uiautomator/library/Android.bp
@@ -35,7 +35,7 @@
     ],
     installable: false,
     flags: [
-        "-stubpackages com.android.uiautomator.core:com.android.uiautomator.testrunner",
+        "--stub-packages com.android.uiautomator.core:com.android.uiautomator.testrunner",
     ],
 
     check_api: {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 1c247a6..807fa48 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7555,6 +7555,12 @@
      * the activity to be restarted). Otherwise, this will be used the next
      * time the activity is visible.
      *
+     * <aside class="note"><b>Note:</b> Device manufacturers can configure devices to override
+     *    (ignore) calls to this method to improve the layout of orientation-restricted apps. See
+     *    <a href="{@docRoot}guide/practices/device-compatibility-mode">
+     *      Device compatibility mode</a>.
+     * </aside>
+     *
      * @param requestedOrientation An orientation constant as used in
      * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
      */
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index fc3bb02..a1fa404 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2239,9 +2239,6 @@
 
         private void visitUris(@NonNull Consumer<Uri> visitor) {
             visitIconUri(visitor, getIcon());
-            if (actionIntent != null) {
-                actionIntent.visitUris(visitor);
-            }
         }
 
         @Override
@@ -2957,21 +2954,6 @@
             }
         }
 
-        // allPendingIntents should contain all associated intents after parcelling, but it may also
-        // contain intents added by the app to extras for their own purposes. We only care about
-        // checking the intents known and used by system_server, to avoid the confused deputy issue.
-        List<PendingIntent> pendingIntents = Arrays.asList(contentIntent, deleteIntent,
-                fullScreenIntent);
-        for (PendingIntent intent : pendingIntents) {
-            if (intent != null) {
-                intent.visitUris(visitor);
-            }
-        }
-
-        if (mBubbleMetadata != null) {
-            mBubbleMetadata.visitUris(visitor);
-        }
-
         if (extras != null) {
             visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class));
             visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class));
@@ -3043,28 +3025,15 @@
                 callPerson.visitUris(visitor);
             }
             visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class));
+        }
 
-            // Extras for MediaStyle.
-            PendingIntent deviceIntent = extras.getParcelable(EXTRA_MEDIA_REMOTE_INTENT,
-                    PendingIntent.class);
-            if (deviceIntent != null) {
-                deviceIntent.visitUris(visitor);
-            }
+        if (mBubbleMetadata != null) {
+            visitIconUri(visitor, mBubbleMetadata.getIcon());
+        }
 
-            if (extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) {
-                WearableExtender extender = new WearableExtender(this);
-                extender.visitUris(visitor);
-            }
-
-            if (extras.containsKey(TvExtender.EXTRA_TV_EXTENDER)) {
-                TvExtender extender = new TvExtender(this);
-                extender.visitUris(visitor);
-            }
-
-            if (extras.containsKey(CarExtender.EXTRA_CAR_EXTENDER)) {
-                CarExtender extender = new CarExtender(this);
-                extender.visitUris(visitor);
-            }
+        if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) {
+            WearableExtender extender = new WearableExtender(this);
+            extender.visitUris(visitor);
         }
     }
 
@@ -11459,16 +11428,6 @@
             }
         }
 
-        private void visitUris(@NonNull Consumer<Uri> visitor) {
-            visitIconUri(visitor, getIcon());
-            if (mPendingIntent != null) {
-                mPendingIntent.visitUris(visitor);
-            }
-            if (mDeleteIntent != null) {
-                mDeleteIntent.visitUris(visitor);
-            }
-        }
-
         /**
          * Builder to construct a {@link BubbleMetadata} object.
          */
@@ -12667,9 +12626,6 @@
         }
 
         private void visitUris(@NonNull Consumer<Uri> visitor) {
-            if (mDisplayIntent != null) {
-                mDisplayIntent.visitUris(visitor);
-            }
             for (Action action : mActions) {
                 action.visitUris(visitor);
             }
@@ -12829,12 +12785,6 @@
             return mUnreadConversation;
         }
 
-        private void visitUris(@NonNull Consumer<Uri> visitor) {
-            if (mUnreadConversation != null) {
-                mUnreadConversation.visitUris(visitor);
-            }
-        }
-
         /**
          * A class which holds the unread messages from a conversation.
          */
@@ -12986,16 +12936,7 @@
                         onRead,
                         participants, b.getLong(KEY_TIMESTAMP));
             }
-
-            private void visitUris(@NonNull Consumer<Uri> visitor) {
-                if (mReadPendingIntent != null) {
-                    mReadPendingIntent.visitUris(visitor);
-                }
-                if (mReplyPendingIntent != null) {
-                    mReplyPendingIntent.visitUris(visitor);
-                }
-            }
-        }
+        };
 
         /**
          * Builder class for {@link CarExtender.UnreadConversation} objects.
@@ -13318,15 +13259,6 @@
         public boolean isSuppressShowOverApps() {
             return mSuppressShowOverApps;
         }
-
-        private void visitUris(@NonNull Consumer<Uri> visitor) {
-            if (mContentIntent != null) {
-                mContentIntent.visitUris(visitor);
-            }
-            if (mDeleteIntent != null) {
-                mDeleteIntent.visitUris(visitor);
-            }
-        }
     }
 
     /**
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 1ac08ac..3714e5d 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -44,8 +44,6 @@
 import android.content.pm.PackageManager.ResolveInfoFlagsBits;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -71,7 +69,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
-import java.util.function.Consumer;
 
 /**
  * A description of an Intent and target action to perform with it.  Instances
@@ -1471,21 +1468,6 @@
         return sb.toString();
     }
 
-    /**
-     * See {@link Intent#visitUris(Consumer)}.
-     *
-     * @hide
-     */
-    public void visitUris(@NonNull Consumer<Uri> visitor) {
-        if (android.app.Flags.visitRiskyUris()) {
-            Intent intent = Binder.withCleanCallingIdentity(this::getIntent);
-
-            if (intent != null) {
-                intent.visitUris(visitor);
-            }
-        }
-    }
-
     /** @hide */
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
diff --git a/core/java/android/app/Person.java b/core/java/android/app/Person.java
index 041866001..96f6f4e 100644
--- a/core/java/android/app/Person.java
+++ b/core/java/android/app/Person.java
@@ -189,7 +189,7 @@
      */
     public void visitUris(@NonNull Consumer<Uri> visitor) {
         visitor.accept(getIconUri());
-        if (Flags.visitRiskyUris()) {
+        if (Flags.visitPersonUri()) {
             if (mUri != null && !mUri.isEmpty()) {
                 visitor.accept(Uri.parse(mUri));
             }
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 73ac263..f751a23 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -46,10 +46,13 @@
 }
 
 flag {
-  name: "visit_risky_uris"
+  name: "visit_person_uri"
   namespace: "systemui"
-  description: "Guards the security fix that ensures all URIs in intents and Person.java are valid"
+  description: "Guards the security fix that ensures all URIs Person.java are valid"
   bug: "281044385"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
 
 # vvv Prototypes for using app icons in notifications vvv
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index 470b1ec..1977a39 100644
--- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -24,6 +24,8 @@
  import android.os.Bundle;
  import android.app.ondeviceintelligence.Feature;
  import android.app.ondeviceintelligence.FeatureDetails;
+ import android.app.ondeviceintelligence.InferenceInfo;
+ import java.util.List;
  import android.app.ondeviceintelligence.IDownloadCallback;
  import android.app.ondeviceintelligence.IListFeaturesCallback;
  import android.app.ondeviceintelligence.IFeatureCallback;
@@ -72,4 +74,6 @@
                     in IStreamingResponseCallback streamingCallback) = 8;
 
       String getRemoteServicePackageName() = 9;
+
+      List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) = 10;
  }
diff --git a/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl b/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl
new file mode 100644
index 0000000..6d70fc4
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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.app.ondeviceintelligence;
+
+/**
+  * @hide
+  */
+parcelable InferenceInfo;
diff --git a/core/java/android/app/ondeviceintelligence/InferenceInfo.java b/core/java/android/app/ondeviceintelligence/InferenceInfo.java
new file mode 100644
index 0000000..5557a81
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/InferenceInfo.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2024 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.app.ondeviceintelligence;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class represents the information related to an inference event to track the resource usage
+ * as a function of inference time.
+ *
+ * @hide
+ */
+public class InferenceInfo implements Parcelable {
+
+    /**
+     * Uid for the caller app.
+     */
+    private final int uid;
+
+    /**
+     * Inference start time (milliseconds from the epoch time).
+     */
+    private final long startTimeMs;
+
+    /**
+     * Inference end time (milliseconds from the epoch time).
+     */
+    private final long endTimeMs;
+
+    /**
+     * Suspended time in milliseconds.
+     */
+    private final long suspendedTimeMs;
+
+    /**
+     * Constructs an InferenceInfo object with the specified parameters.
+     *
+     * @param uid             Uid for the caller app.
+     * @param startTimeMs     Inference start time (milliseconds from the epoch time).
+     * @param endTimeMs       Inference end time (milliseconds from the epoch time).
+     * @param suspendedTimeMs Suspended time in milliseconds.
+     */
+    public InferenceInfo(int uid, long startTimeMs, long endTimeMs,
+            long suspendedTimeMs) {
+        this.uid = uid;
+        this.startTimeMs = startTimeMs;
+        this.endTimeMs = endTimeMs;
+        this.suspendedTimeMs = suspendedTimeMs;
+    }
+
+    /**
+     * Constructs an InferenceInfo object from a Parcel.
+     *
+     * @param in The Parcel to read the object's data from.
+     */
+    protected InferenceInfo(Parcel in) {
+        uid = in.readInt();
+        startTimeMs = in.readLong();
+        endTimeMs = in.readLong();
+        suspendedTimeMs = in.readLong();
+    }
+
+
+    /**
+     * Writes the object's data to the provided Parcel.
+     *
+     * @param dest The Parcel to write the object's data to.
+     * @param flags Additional flags about how the object should be written.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(uid);
+        dest.writeLong(startTimeMs);
+        dest.writeLong(endTimeMs);
+        dest.writeLong(suspendedTimeMs);
+    }
+
+    /**
+     * Returns the UID for the caller app.
+     *
+     * @return the UID for the caller app.
+     */
+    public int getUid() {
+        return uid;
+    }
+
+    /**
+     * Returns the inference start time in milliseconds from the epoch time.
+     *
+     * @return the inference start time in milliseconds from the epoch time.
+     */
+    public long getStartTimeMs() {
+        return startTimeMs;
+    }
+
+    /**
+     * Returns the inference end time in milliseconds from the epoch time.
+     *
+     * @return the inference end time in milliseconds from the epoch time.
+     */
+    public long getEndTimeMs() {
+        return endTimeMs;
+    }
+
+    /**
+     * Returns the suspended time in milliseconds.
+     *
+     * @return the suspended time in milliseconds.
+     */
+    public long getSuspendedTimeMs() {
+        return suspendedTimeMs;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+
+    public static final @android.annotation.NonNull Parcelable.Creator<InferenceInfo> CREATOR
+            = new Parcelable.Creator<InferenceInfo>() {
+        @Override
+        public InferenceInfo[] newArray(int size) {
+            return new InferenceInfo[size];
+        }
+
+        @Override
+        public InferenceInfo createFromParcel(@android.annotation.NonNull Parcel in) {
+            return new InferenceInfo(in);
+        }
+    };
+
+    /**
+     * Builder class for creating instances of {@link InferenceInfo}.
+     */
+    public static class Builder {
+        private int uid;
+        private long startTimeMs;
+        private long endTimeMs;
+        private long suspendedTimeMs;
+
+        /**
+         * Sets the UID for the caller app.
+         *
+         * @param uid the UID for the caller app.
+         * @return the Builder instance.
+         */
+        public Builder setUid(int uid) {
+            this.uid = uid;
+            return this;
+        }
+
+        /**
+         * Sets the inference start time in milliseconds from the epoch time.
+         *
+         * @param startTimeMs the inference start time in milliseconds from the epoch time.
+         * @return the Builder instance.
+         */
+        public Builder setStartTimeMs(long startTimeMs) {
+            this.startTimeMs = startTimeMs;
+            return this;
+        }
+
+        /**
+         * Sets the inference end time in milliseconds from the epoch time.
+         *
+         * @param endTimeMs the inference end time in milliseconds from the epoch time.
+         * @return the Builder instance.
+         */
+        public Builder setEndTimeMs(long endTimeMs) {
+            this.endTimeMs = endTimeMs;
+            return this;
+        }
+
+        /**
+         * Sets the suspended time in milliseconds.
+         *
+         * @param suspendedTimeMs the suspended time in milliseconds.
+         * @return the Builder instance.
+         */
+        public Builder setSuspendedTimeMs(long suspendedTimeMs) {
+            this.suspendedTimeMs = suspendedTimeMs;
+            return this;
+        }
+
+        /**
+         * Builds and returns an instance of {@link InferenceInfo}.
+         *
+         * @return an instance of {@link InferenceInfo}.
+         */
+        public InferenceInfo build() {
+            return new InferenceInfo(uid, startTimeMs, endTimeMs,
+                    suspendedTimeMs);
+        }
+    }
+}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index a37f51b..937a9cd 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -496,6 +496,24 @@
         }
     }
 
+    /**
+     * This is primarily intended to be used to attribute/blame on-device intelligence power usage,
+     * via the configured remote implementation, to its actual caller.
+     *
+     * @param startTimeEpochMillis epoch millis used to filter the InferenceInfo events.
+     * @return InferenceInfo events since the passed in startTimeEpochMillis.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) {
+        try {
+            return mService.getLatestInferenceInfo(startTimeEpochMillis);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
 
     /** Request inference with provided Bundle and Params. */
     public static final int REQUEST_TYPE_INFERENCE = 0;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 02d62a2..2e60cb0 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -106,7 +106,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.TimeZone;
-import java.util.function.Consumer;
 
 /**
  * An intent is an abstract description of an operation to be performed.  It
@@ -8330,27 +8329,6 @@
         }
     }
 
-    /**
-     * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
-     * grants will need to be issued to ensure the recipient of this object is able to render its
-     * contents.
-     * See b/281044385 for more context and examples about what happens when this isn't done
-     * correctly.
-     *
-     * @hide
-     */
-    public void visitUris(@NonNull Consumer<Uri> visitor) {
-        if (android.app.Flags.visitRiskyUris()) {
-            visitor.accept(mData);
-            if (mSelector != null) {
-                mSelector.visitUris(visitor);
-            }
-            if (mOriginalIntent != null) {
-                mOriginalIntent.visitUris(visitor);
-            }
-        }
-    }
-
     public static Intent getIntentOld(String uri) throws URISyntaxException {
         Intent intent = getIntentOld(uri, 0);
         intent.mLocalFlags |= LOCAL_FLAG_FROM_URI;
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 86d061c..e895d7b 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -823,6 +823,16 @@
     }
 
     /**
+     * Returns the number of actions in the filter, or {@code 0} if there are no actions.
+     * <p> This method provides a safe alternative to {@link #countActions()}, which
+     * may throw an exception if there are no actions.
+     * @hide
+     */
+    public final int safeCountActions() {
+        return mActions == null ? 0 : mActions.size();
+    }
+
+    /**
      * Return an action in the filter.
      */
     public final String getAction(int index) {
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 93cc71b..df27f9d 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -696,14 +696,11 @@
      * Otherwise it'll return the same list as {@link UserManager#getUserProfiles()} would.
      *
      * <p>To get hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @SuppressLint("RequiresPermission")
     @RequiresPermission(conditional = true,
             anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -765,19 +762,16 @@
      * </ul>
      *
      * <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * @param packageName The specific package to query. If null, it checks all installed packages
      *            in the profile.
      * @param user The UserHandle of the profile.
      * @return List of launchable activities. Can be an empty list but will not be null.
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @SuppressLint("RequiresPermission")
     @RequiresPermission(conditional = true,
             anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -821,18 +815,15 @@
      * to distinguish it from other users (eg, badges).
      *
      * <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * @param userHandle user handle of the user for which LauncherUserInfo is requested.
      * @return the {@link LauncherUserInfo} object related to the user specified, null in case
      * the user is inaccessible.
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @Nullable
     @SuppressLint("RequiresPermission")
     @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
@@ -874,13 +865,8 @@
      * </p>
      *
      * <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * @param packageName the package for which intent sender to launch App Market Activity is
      *                    required.
@@ -888,6 +874,8 @@
      * @return {@link IntentSender} object which launches the App Market Activity, null in case
      *         there is no such activity.
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @Nullable
     @SuppressLint("RequiresPermission")
     @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
@@ -914,18 +902,15 @@
      * user at creation.
      *
      * <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * @param userHandle the user for which installed system packages are required.
      * @return {@link List} of {@link String}, representing the package name of the installed
      *        package. Can be empty but not null.
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
     @NonNull
     @SuppressLint("RequiresPermission")
@@ -952,6 +937,8 @@
      * successful, null otherwise.
      * @hide
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @Nullable
     @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
     @RequiresPermission(conditional = true,
@@ -969,18 +956,15 @@
      * returns null.
      *
      * <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * @param intent The intent to find a match for.
      * @param user The profile to look in for a match.
      * @return An activity info object if there is a match.
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @SuppressLint("RequiresPermission")
     @RequiresPermission(conditional = true,
             anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1034,19 +1018,16 @@
      * Starts a Main activity in the specified profile.
      *
      * <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * @param component The ComponentName of the activity to launch
      * @param user The UserHandle of the profile
      * @param sourceBounds The Rect containing the source bounds of the clicked icon
      * @param opts Options to pass to startActivity
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @SuppressLint("RequiresPermission")
     @RequiresPermission(conditional = true,
             anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1088,19 +1069,16 @@
      * package in the specified profile.
      *
      * <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * @param component The ComponentName of the package to launch settings for.
      * @param user The UserHandle of the profile
      * @param sourceBounds The Rect containing the source bounds of the clicked icon
      * @param opts Options to pass to startActivity
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @SuppressLint("RequiresPermission")
     @RequiresPermission(conditional = true,
             anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1216,19 +1194,16 @@
      * Checks if the package is installed and enabled for a profile.
      *
      * <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * @param packageName The package to check.
      * @param user The UserHandle of the profile.
      *
      * @return true if the package exists and is enabled.
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @SuppressLint("RequiresPermission")
     @RequiresPermission(conditional = true,
             anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1250,13 +1225,8 @@
      * app and the launcher.
      *
      * <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * <p>Note: This just returns whatever extras were provided to the system, <em>which might
      * even be {@code null}.</em>
@@ -1269,6 +1239,8 @@
      * @see Callback#onPackagesSuspended(String[], UserHandle, Bundle)
      * @see PackageManager#isPackageSuspended()
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @SuppressLint("RequiresPermission")
     @RequiresPermission(conditional = true,
             anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1287,18 +1259,15 @@
      * {@code PackageManager.setDistractingPackageRestrictions(String[], int)}.
      *
      * <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * @param packageName The package for which to check.
      * @param user the {@link UserHandle} of the profile.
      * @return
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @SuppressLint("RequiresPermission")
     @RequiresPermission(conditional = true,
             anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1317,13 +1286,8 @@
      * Returns {@link ApplicationInfo} about an application installed for a specific user profile.
      *
      * <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * @param packageName The package name of the application
      * @param flags Additional option flags {@link PackageManager#getApplicationInfo}
@@ -1333,6 +1297,8 @@
      *         {@code null} if the package isn't installed for the given profile, or the profile
      *         isn't enabled.
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @SuppressLint("RequiresPermission")
     @RequiresPermission(conditional = true,
             anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1386,19 +1352,16 @@
      * throw a {@link SecurityException} unless the caller has the same UID as the target app's.
      *
      * <p>If the user in question is a hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * @param component The activity to check.
      * @param user The UserHandle of the profile.
      *
      * @return true if the activity exists and is enabled.
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @SuppressLint("RequiresPermission")
     @RequiresPermission(conditional = true,
             anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1961,16 +1924,13 @@
      * Registers a callback for changes to packages in this user and managed profiles.
      *
      * <p>To receive callbacks for hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * @param callback The callback to register.
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @SuppressLint("RequiresPermission")
     @RequiresPermission(conditional = true,
             anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -1982,17 +1942,14 @@
      * Registers a callback for changes to packages in this user and managed profiles.
      *
      * <p>To receive callbacks for hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * @param callback The callback to register.
      * @param handler that should be used to post callbacks on, may be null.
      */
+    // Alternatively, a system app can access this api for private profile if they've been granted
+    // with the {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL} permission.
     @SuppressLint("RequiresPermission")
     @RequiresPermission(conditional = true,
             anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
@@ -2447,13 +2404,8 @@
      * the session owner to retrieve these details.
      *
      * <p>To receive callbacks for hidden profile {@link UserManager#USER_TYPE_PROFILE_PRIVATE},
-     * caller should have either:</p>
-     * <ul>
-     * <li>the privileged {@code android.Manifest.permission#ACCESS_HIDDEN_PROFILES_FULL}
-     * permission</li>
-     * <li>the normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES} permission and the
-     * {@link android.app.role.RoleManager#ROLE_HOME} role. </li>
-     * </ul>
+     * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
+     * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
      * @see PackageInstaller#getAllSessions()
      */
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index a77e076..f123a96 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -101,6 +101,11 @@
     private static final String TAG = OnDeviceSandboxedInferenceService.class.getSimpleName();
 
     /**
+     * @hide
+     */
+    public static final String INFERENCE_INFO_BUNDLE_KEY = "inference_info";
+
+    /**
      * The {@link Intent} that must be declared as handled by the service. To be supported, the
      * service must also require the
      * {@link android.Manifest.permission#BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 9589785..8271caf 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -29,6 +29,7 @@
 
 import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION;
 import static com.android.window.flags.Flags.noConsecutiveVisibilityEvents;
+import static com.android.window.flags.Flags.noVisibilityEventOnDisplayStateChange;
 import static com.android.window.flags.Flags.offloadColorExtraction;
 import static com.android.window.flags.Flags.windowSessionRelayoutInfo;
 
@@ -2387,8 +2388,10 @@
                     @Override
                     public void onDisplayChanged(int displayId) {
                         if (mDisplay.getDisplayId() == displayId) {
-                            boolean forceReport = mIsWearOs
-                                    && mDisplay.getState() != Display.STATE_DOZE_SUSPEND;
+                            boolean forceReport =
+                                    !noVisibilityEventOnDisplayStateChange()
+                                            && mIsWearOs
+                                            && mDisplay.getState() != Display.STATE_DOZE_SUSPEND;
                             reportVisibility(forceReport);
                         }
                     }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 42bf420..7b7ead4 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -963,7 +963,7 @@
      * true:
      * <ul>
      *     <li>Activity has requested orientation more than two times within one-second timer
-     *     <li>Activity is not letterboxed for fixed orientation
+     *     <li>Activity is not letterboxed for fixed-orientation apps
      * </ul>
      *
      * <p>Setting this property to {@code false} informs the system that the app must be
@@ -1055,22 +1055,22 @@
      * for an app to inform the system that the app should be excluded from the camera compatibility
      * force rotation treatment.
      *
-     * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
-     * orientation of the device and set opposite to natural orientation for a landscape app
-     * window. Mismatch between them can lead to camera issues like sideways or stretched
+     * <p>The camera compatibility treatment aligns portrait app windows with the natural
+     * orientation of the device and landscape app windows opposite the device natural orientation.
+     * Mismatch between the orientations can lead to camera issues like a sideways or stretched
      * viewfinder since this is one of the strongest assumptions that apps make when they implement
-     * camera previews. Since app and natural display orientations aren't guaranteed to match, the
-     * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
+     * camera previews. Since app and device natural orientations aren't guaranteed to match, the
+     * rotation can cause letterboxing. The forced rotation is triggered as soon as an app opens the
      * camera and is removed once camera is closed.
      *
-     * <p>The camera compatibility can be enabled by device manufacturers on displays that have the
-     * ignore requested orientation display setting enabled (enables compatibility mode for fixed
-     * orientation on Android 12 (API level 31) or higher; see
-     * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
-     * letterboxing</a> for more details).
+     * <p>Camera compatibility can be enabled by device manufacturers on displays that have the
+     * ignore requested orientation display setting enabled, which enables compatibility mode for
+     * fixed-orientation apps on Android 12 (API level 31) or higher. See
+     * <a href="{@docRoot}guide/practices/device-compatibility-mode">Device compatibility mode</a>
+     * for more details.
      *
      * <p>With this property set to {@code true} or unset, the system may apply the force rotation
-     * treatment to fixed orientation activities. Device manufacturers can exclude packages from the
+     * treatment to fixed-orientation activities. Device manufacturers can exclude packages from the
      * treatment using their discretion to improve display compatibility.
      *
      * <p>With this property set to {@code false}, the system will not apply the force rotation
@@ -1093,12 +1093,12 @@
      * for an app to inform the system that the app should be excluded from the activity "refresh"
      * after the camera compatibility force rotation treatment.
      *
-     * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
-     * orientation of the device and set opposite to natural orientation for a landscape app
-     * window. Mismatch between them can lead to camera issues like sideways or stretched
+     * <p>The camera compatibility treatment aligns portrait app windows with the natural
+     * orientation of the device and landscape app windows opposite the device natural orientation.
+     * Mismatch between the orientations can lead to camera issues like a sideways or stretched
      * viewfinder since this is one of the strongest assumptions that apps make when they implement
-     * camera previews. Since app and natural display orientations aren't guaranteed to match, the
-     * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
+     * camera previews. Since app and device natural orientations aren't guaranteed to match, the
+     * rotation can cause letterboxing. The forced rotation is triggered as soon as an app opens the
      * camera and is removed once camera is closed.
      *
      * <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed ->
@@ -1109,10 +1109,10 @@
      * rotation.
      *
      * <p>The camera compatibility can be enabled by device manufacturers on displays that have the
-     * ignore requested orientation display setting enabled (enables compatibility mode for fixed
-     * orientation on Android 12 (API level 31) or higher; see
-     * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
-     * letterboxing</a> for more details).
+     * ignore requested orientation display setting enabled, which enables compatibility mode for
+     * fixed-orientation apps on Android 12 (API level 31) or higher. See
+     * <a href="{@docRoot}guide/practices/device-compatibility-mode">Device compatibility mode</a>
+     * for more details.
      *
      * <p>With this property set to {@code true} or unset, the system may "refresh" activity after
      * the force rotation treatment. Device manufacturers can exclude packages from the "refresh"
@@ -1140,12 +1140,11 @@
      * "stopped -> resumed".
      *
      * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
-     * orientation of the device and set opposite to natural orientation for a landscape app
-     * window. Mismatch between them can lead to camera issues like sideways or stretched
-     * viewfinder since this is one of the strongest assumptions that apps make when they implement
-     * camera previews. Since app and natural display orientations aren't guaranteed to match, the
-     * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
-     * camera and is removed once camera is closed.
+     * orientation of the device. Mismatch between the orientations can lead to camera issues like a
+     * sideways or stretched viewfinder since this is one of the strongest assumptions that apps
+     * make when they implement camera previews. Since app and natural display orientations aren't
+     * guaranteed to match, the rotation can cause letterboxing. The forced rotation is triggered as
+     * soon as app opens the camera and is removed once camera is closed.
      *
      * <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed ->
      * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle
@@ -1154,10 +1153,10 @@
      * to sideways or stretching issues persisting even after force rotation.
      *
      * <p>The camera compatibility can be enabled by device manufacturers on displays that have the
-     * ignore requested orientation display setting enabled (enables compatibility mode for fixed
-     * orientation on Android 12 (API level 31) or higher; see
-     * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
-     * letterboxing</a> for more details).
+     * ignore requested orientation display setting enabled, which enables compatibility mode for
+     * fixed-orientation apps on Android 12 (API level 31) or higher. See
+     * <a href="{@docRoot}guide/practices/device-compatibility-mode">Device compatibility mode</a>
+     * for more details.
      *
      * <p>Device manufacturers can override packages to "refresh" via "resumed -> paused -> resumed"
      * cycle using their discretion to improve display compatibility.
@@ -1203,7 +1202,7 @@
      * <p>With this property set to {@code true} or unset, device manufacturers can override
      * orientation for the app using their discretion to improve display compatibility.
      *
-     * <p>With this property set to {@code false}, device manufactured per-app override for
+     * <p>With this property set to {@code false}, device manufacturer per-app override for
      * orientation won't be applied.
      *
      * <p><b>Syntax:</b>
@@ -1227,15 +1226,15 @@
      * <p>When this compat override is enabled and while display is fixed to the landscape natural
      * orientation, the orientation requested by the activity will be still respected by bounds
      * resolution logic. For instance, if an activity requests portrait orientation, then activity
-     * will appear in the letterbox mode for fixed orientation with the display rotated to the
-     * lanscape natural orientation.
+     * appears in letterbox mode for fixed-orientation apps with the display rotated to the lanscape
+     * natural orientation.
      *
      * <p>The treatment is disabled by default but device manufacturers can enable the treatment
      * using their discretion to improve display compatibility on displays that have the ignore
-     * orientation request display setting enabled by OEMs on the device (enables compatibility mode
-     * for fixed orientation on Android 12 (API level 31) or higher; see
-     * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
-     * letterboxing</a> for more details).
+     * orientation request display setting enabled by OEMs on the device, which enables
+     * compatibility mode for fixed-orientation apps on Android 12 (API level 31) or higher. See
+     * <a href="{@docRoot}guide/practices/device-compatibility-mode">Device compatibility mode</a>
+     * for more details.
      *
      * <p>With this property set to {@code true} or unset, the system wiil use landscape display
      * orientation when the following conditions are met:
@@ -1246,7 +1245,7 @@
      *     <li>Device manufacturer enabled the treatment.
      * </ul>
      *
-     * <p>With this property set to {@code false}, device manufactured per-app override for
+     * <p>With this property set to {@code false}, device manufacturer per-app override for
      * display orientation won't be applied.
      *
      * <p><b>Syntax:</b>
@@ -1344,13 +1343,11 @@
      * see {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE} to
      * disable the full-screen option only.
      *
-     * <p>The user override is intended to improve the app experience on devices
-     * that have the ignore orientation request display setting enabled by OEMs
-     * (enables compatibility mode for fixed orientation on Android 12 (API
-     * level 31) or higher; see
-     * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-compatibility-mode">
-     * Large screen compatibility mode</a>
-     * for more details).
+     * <p>The user override is intended to improve the app experience on devices that have the
+     * ignore orientation request display setting enabled by OEMs, which enables compatibility mode
+     * for fixed-orientation apps on Android 12 (API level 31) or higher. See
+     * <a href="{@docRoot}guide/practices/device-compatibility-mode">Device compatibility mode</a>
+     * for more details.
      *
      * <p>To opt out of the user aspect ratio compatibility override, add this property
      * to your app manifest and set the value to {@code false}. Your app will be excluded
@@ -1383,13 +1380,11 @@
      * <p>When users apply the full-screen compatibility override, the orientation
      * of the activity is forced to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER}.
      *
-     * <p>The user override is intended to improve the app experience on devices
-     * that have the ignore orientation request display setting enabled by OEMs
-     * (enables compatibility mode for fixed orientation on Android 12 (API
-     * level 31) or higher; see
-     * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-compatibility-mode">
-     * Large screen compatibility mode</a>
-     * for more details).
+     * <p>The user override is intended to improve the app experience on devices that have the
+     * ignore orientation request display setting enabled by OEMs, which enables compatibility mode
+     * for fixed-orientation apps on Android 12 (API level 31) or higher. See
+     * <a href="{@docRoot}guide/practices/device-compatibility-mode">Device compatibility mode</a>
+     * for more details.
      *
      * <p>To opt out of the full-screen option of the user aspect ratio compatibility
      * override, add this property to your app manifest and set the value to {@code false}.
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 53d9429..0d4c556 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -3010,16 +3010,34 @@
                 mLastAutofilledData.put(view.getAutofillId(), targetValue);
             }
             view.setAutofilled(true, hideHighlight);
+            if (sDebug) {
+                Log.d(TAG, "View " + view.getAutofillId() + " autofilled synchronously.");
+            }
             try {
                 mService.setViewAutofilled(mSessionId, view.getAutofillId(), mContext.getUserId());
             } catch (RemoteException e) {
                 // The failure could be a consequence of something going wrong on the server side.
                 // Do nothing here since it's just logging, but it's possible follow-up actions may
                 // fail.
+                Log.w(TAG, "Unable to log due to " + e);
+            }
+        } else {
+            if (sDebug) {
+                Log.d(TAG, "View " + view.getAutofillId() + " " + view.getClass().toString()
+                        + " from " + view.getClass().getPackageName()
+                        + " : didn't fill in synchronously. It may fill asynchronously.");
             }
         }
     }
 
+    /**
+     * Returns String with text "null" if the object is null, or the actual string represented by
+     * the object.
+     */
+    private @NonNull String getString(Object obj) {
+        return obj == null ? "null" : obj.toString();
+    }
+
     private void onGetCredentialException(int sessionId, AutofillId id, String errorType,
             String errorMsg) {
         synchronized (mLock) {
@@ -4308,14 +4326,16 @@
 
             if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) {
                 if (sVerbose) {
-                    Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleTrackedIds);
+                    Log.v(TAG, "No more visible tracked save ids. Invisible = "
+                            + mInvisibleTrackedIds);
                 }
                 finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED);
 
             }
             if (mVisibleDialogTrackedIds.isEmpty()) {
                 if (sVerbose) {
-                    Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleDialogTrackedIds);
+                    Log.v(TAG, "No more visible tracked fill dialog ids. Invisible = "
+                            + mInvisibleDialogTrackedIds);
                 }
                 processNoVisibleTrackedAllViews();
             }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 15f9cff8..3c5623f 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1044,11 +1044,6 @@
         public int getActionTag() {
             return SET_PENDING_INTENT_TEMPLATE_TAG;
         }
-
-        @Override
-        public void visitUris(@NonNull Consumer<Uri> visitor) {
-            mPendingIntentTemplate.visitUris(visitor);
-        }
     }
 
     /**
@@ -1528,11 +1523,6 @@
         public int getActionTag() {
             return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG;
         }
-
-        @Override
-        public void visitUris(@NonNull Consumer<Uri> visitor) {
-            mIntent.visitUris(visitor);
-        }
     }
 
     /**
@@ -1611,11 +1601,6 @@
         public int getActionTag() {
             return SET_ON_CLICK_RESPONSE_TAG;
         }
-
-        @Override
-        public void visitUris(@NonNull Consumer<Uri> visitor) {
-            mResponse.visitUris(visitor);
-        }
     }
 
     /** Helper action to configure handwriting delegation via {@link PendingIntent}. */
@@ -1663,11 +1648,6 @@
         public int getActionTag() {
             return SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG;
         }
-
-        @Override
-        public void visitUris(@NonNull Consumer<Uri> visitor) {
-            mPendingIntent.visitUris(visitor);
-        }
     }
 
     /**
@@ -1738,11 +1718,6 @@
         public int getActionTag() {
             return SET_ON_CHECKED_CHANGE_RESPONSE_TAG;
         }
-
-        @Override
-        public void visitUris(@NonNull Consumer<Uri> visitor) {
-            mResponse.visitUris(visitor);
-        }
     }
 
     /** @hide **/
@@ -2302,10 +2277,6 @@
                     final Icon icon = (Icon) getParameterValue(null);
                     if (icon != null) visitIconUri(icon, visitor);
                     break;
-                case INTENT:
-                    final Intent intent = (Intent) getParameterValue(null);
-                    if (intent != null) intent.visitUris(visitor);
-                    break;
                 // TODO(b/281044385): Should we do anything about type BUNDLE?
             }
         }
@@ -7226,20 +7197,6 @@
             mElementNames = parcel.createStringArrayList();
         }
 
-        /**
-         * See {@link RemoteViews#visitUris(Consumer)}.
-         *
-         * @hide
-         */
-        public void visitUris(@NonNull Consumer<Uri> visitor) {
-            if (mPendingIntent != null) {
-                mPendingIntent.visitUris(visitor);
-            }
-            if (mFillIntent != null) {
-                mFillIntent.visitUris(visitor);
-            }
-        }
-
         private void handleViewInteraction(
                 View v,
                 InteractionHandler handler) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fd3837f..f7e0ec8 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -14051,6 +14051,9 @@
 
     @Override
     public void autofill(AutofillValue value) {
+        if (android.view.autofill.Helper.sVerbose) {
+            Log.v(LOG_TAG, "autofill() called on textview for id:" + getAutofillId());
+        }
         if (!isTextAutofillable()) {
             Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this);
             return;
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index 150b04e..01c78a0 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -31,4 +31,11 @@
   metadata {
     purpose: PURPOSE_BUGFIX
   }
-}
\ No newline at end of file
+}
+
+flag {
+  name: "no_visibility_event_on_display_state_change"
+  namespace: "wear_frameworks"
+  description: "Prevent the system from sending visibility event on display state change."
+  bug: "331725519"
+}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 87ede4a..0a4762d 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -180,3 +180,14 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "windowing_sdk"
+    name: "rear_display_disable_force_desktop_system_decorations"
+    description: "Block system decorations from being added to a rear display when desktop mode is forced"
+    bug: "346103150"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 2194c89..40d760e 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -537,8 +537,13 @@
         }
         mContentParent.requestApplyInsets();
         final Callback cb = getCallback();
-        if (cb != null && !isDestroyed()) {
-            cb.onContentChanged();
+        if (!isDestroyed()) {
+            if (cb != null) {
+                cb.onContentChanged();
+            }
+            if (mDecorContentParent != null) {
+                mDecorContentParent.notifyContentChanged();
+            }
         }
         mContentParentExplicitlySet = true;
     }
@@ -568,8 +573,13 @@
         }
         mContentParent.requestApplyInsets();
         final Callback cb = getCallback();
-        if (cb != null && !isDestroyed()) {
-            cb.onContentChanged();
+        if (!isDestroyed()) {
+            if (cb != null) {
+                cb.onContentChanged();
+            }
+            if (mDecorContentParent != null) {
+                mDecorContentParent.notifyContentChanged();
+            }
         }
         mContentParentExplicitlySet = true;
     }
@@ -586,8 +596,13 @@
         mContentParent.addView(view, params);
         mContentParent.requestApplyInsets();
         final Callback cb = getCallback();
-        if (cb != null && !isDestroyed()) {
-            cb.onContentChanged();
+        if (!isDestroyed()) {
+            if (cb != null) {
+                cb.onContentChanged();
+            }
+            if (mDecorContentParent != null) {
+                mDecorContentParent.notifyContentChanged();
+            }
         }
     }
 
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index 6832825..ff57fd4 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -898,6 +898,13 @@
         mDecorToolbar.dismissPopupMenus();
     }
 
+    @Override
+    public void notifyContentChanged() {
+        mLastBaseContentInsets.setEmpty();
+        mLastBaseInnerInsets = WindowInsets.CONSUMED;
+        mLastInnerInsets = WindowInsets.CONSUMED;
+    }
+
     public static class LayoutParams extends MarginLayoutParams {
         public LayoutParams(Context c, AttributeSet attrs) {
             super(c, attrs);
diff --git a/core/java/com/android/internal/widget/DecorContentParent.java b/core/java/com/android/internal/widget/DecorContentParent.java
index ac524f9..8d6cfd1 100644
--- a/core/java/com/android/internal/widget/DecorContentParent.java
+++ b/core/java/com/android/internal/widget/DecorContentParent.java
@@ -22,6 +22,7 @@
 import android.util.SparseArray;
 import android.view.Menu;
 import android.view.Window;
+
 import com.android.internal.view.menu.MenuPresenter;
 
 /**
@@ -49,4 +50,5 @@
     void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates);
     void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates);
     void dismissPopups();
+    void notifyContentChanged();
 }
diff --git a/core/res/res/drawable/ic_swipe_down.xml b/core/res/res/drawable/ic_swipe_down.xml
new file mode 100644
index 0000000..15712d6
--- /dev/null
+++ b/core/res/res/drawable/ic_swipe_down.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M180,600L40,460L82,418L152,488Q146,461 143,434Q140,407 140,380Q140,298 167,221Q194,144 245,80L288,123Q245,179 222.5,244.5Q200,310 200,380Q200,406 203,431.5Q206,457 213,482L278,418L320,460L180,600ZM658,833Q635,841 611.5,840.5Q588,840 566,829L304,707L322,667Q332,647 350,634.5Q368,622 390,620L458,615L346,308Q340,292 347,277.5Q354,263 370,257Q386,251 400.5,258Q415,265 421,281L569,688L469,695L600,756Q607,759 615,759.5Q623,760 630,758L787,701Q818,690 832,659.5Q846,629 835,598L780,448Q774,432 781,417.5Q788,403 804,397Q820,391 834.5,398Q849,405 855,421L910,571Q933,634 905.5,693.5Q878,753 815,776L658,833ZM568,568L514,417Q508,401 515,386.5Q522,372 538,366Q554,360 568.5,367Q583,374 589,390L644,540L568,568ZM681,527L640,414Q634,398 641,383.5Q648,369 664,363Q680,357 694.5,364Q709,371 715,387L756,499L681,527ZM689,605L689,605L689,605Q689,605 689,605Q689,605 689,605L689,605Q689,605 689,605Q689,605 689,605L689,605L689,605Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/immersive_cling_bg_circ.xml b/core/res/res/drawable/immersive_cling_bg.xml
similarity index 67%
rename from core/res/res/drawable/immersive_cling_bg_circ.xml
rename to core/res/res/drawable/immersive_cling_bg.xml
index 4731bbd..de29c32 100644
--- a/core/res/res/drawable/immersive_cling_bg_circ.xml
+++ b/core/res/res/drawable/immersive_cling_bg.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2015 The Android Open Source Project
+  ~ Copyright (C) 2024 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.
@@ -15,12 +15,10 @@
   ~ limitations under the License
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval" >
-
-    <solid android:color="@color/white" />
-
-    <size
-        android:height="56dp"
-        android:width="56dp" />
-
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <corners
+        android:bottomLeftRadius="28dp"
+        android:bottomRightRadius="28dp"/>
+    <solid android:color="?androidprv:attr/materialColorSurfaceContainer" />
 </shape>
diff --git a/core/res/res/drawable/immersive_cling_btn_bg.xml b/core/res/res/drawable/immersive_cling_btn_bg.xml
new file mode 100644
index 0000000..df49e38
--- /dev/null
+++ b/core/res/res/drawable/immersive_cling_btn_bg.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+    <ripple android:color="?android:attr/colorControlHighlight">
+        <item android:id="@android:id/mask">
+            <shape android:shape="rectangle">
+                <solid android:color="@android:color/white" />
+                <corners android:radius="28dp" />
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="rectangle">
+                <corners android:radius="28dp" />
+                <solid android:color="?android:attr/colorAccent" />
+            </shape>
+        </item>
+    </ripple>
+</inset>
\ No newline at end of file
diff --git a/core/res/res/drawable/immersive_cling_light_bg_circ.xml b/core/res/res/drawable/immersive_cling_light_bg_circ.xml
deleted file mode 100644
index df5d5ad..0000000
--- a/core/res/res/drawable/immersive_cling_light_bg_circ.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2015 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval" >
-
-    <solid android:color="#80ffffff" />
-
-    <size
-        android:height="76dp"
-        android:width="76dp" />
-
-</shape>
diff --git a/core/res/res/layout/immersive_mode_cling.xml b/core/res/res/layout/immersive_mode_cling.xml
index 9fd615d..2cde9e6 100644
--- a/core/res/res/layout/immersive_mode_cling.xml
+++ b/core/res/res/layout/immersive_mode_cling.xml
@@ -14,79 +14,67 @@
      limitations under the License.
 -->
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+        android:theme="@android:style/Theme.DeviceDefault.DayNight"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="?android:attr/colorAccent"
+        android:background="@android:drawable/immersive_cling_bg"
         android:gravity="center_vertical"
-        android:paddingBottom="24dp">
+        android:padding="24dp">
 
-    <FrameLayout
-            android:id="@+id/immersive_cling_chevron"
-            android:layout_width="76dp"
-            android:layout_height="76dp"
-            android:layout_marginTop="-24dp"
-            android:layout_centerHorizontal="true">
-
-        <ImageView
-                android:id="@+id/immersive_cling_back_bg_light"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:scaleType="center"
-                android:src="@drawable/immersive_cling_light_bg_circ" />
-
-        <ImageView
-                android:id="@+id/immersive_cling_back_bg"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:scaleType="center"
-                android:src="@drawable/immersive_cling_bg_circ" />
-
-        <ImageView
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:paddingTop="8dp"
-                android:scaleType="center"
-                android:src="@drawable/ic_expand_more_48dp"
-                android:tint="?android:attr/colorAccent"/>
-    </FrameLayout>
+    <!-- The top margin of this icon can be adjusted to push the content down to prevent overlapping
+         with the display cutout. -->
+    <ImageView
+            android:id="@+id/immersive_cling_icon"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:layout_centerHorizontal="true"
+            android:scaleType="fitXY"
+            android:src="@drawable/ic_swipe_down"
+            android:tint="?android:attr/colorAccent"
+            android:tintMode="src_in" />
 
     <TextView
             android:id="@+id/immersive_cling_title"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_below="@id/immersive_cling_chevron"
-            android:paddingEnd="48dp"
-            android:paddingStart="48dp"
-            android:paddingTop="40dp"
+            android:layout_below="@id/immersive_cling_icon"
+            android:layout_marginTop="20dp"
+            android:gravity="center_horizontal"
             android:text="@string/immersive_cling_title"
-            android:textColor="@android:color/white"
-            android:textSize="24sp" />
+            android:textColor="?androidprv:attr/materialColorOnSurface"
+            android:textSize="24sp"
+            android:fontFamily="google-sans" />
 
     <TextView
             android:id="@+id/immersive_cling_description"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_below="@id/immersive_cling_title"
-            android:paddingEnd="48dp"
-            android:paddingStart="48dp"
-            android:paddingTop="12.6dp"
+            android:paddingTop="14dp"
+            android:gravity="center_horizontal"
             android:text="@string/immersive_cling_description"
-            android:textColor="@android:color/white"
-            android:textSize="16sp" />
+            android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+            android:textSize="14sp"
+            android:fontFamily="google-sans" />
 
     <Button
             android:id="@+id/ok"
-            style="@style/Widget.Material.Button.Borderless"
+            style="@style/Widget.Material.Button.Borderless.Colored"
+            android:background="@drawable/immersive_cling_btn_bg"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_alignParentEnd="true"
             android:layout_below="@+id/immersive_cling_description"
-            android:layout_marginEnd="40dp"
-            android:layout_marginTop="18dp"
-            android:paddingEnd="8dp"
-            android:paddingStart="8dp"
+            android:layout_marginTop="24dp"
+            android:paddingStart="18dp"
+            android:paddingEnd="18dp"
+            android:minWidth="48dp"
+            android:minHeight="48dp"
             android:text="@string/immersive_cling_positive"
-            android:textColor="@android:color/white"
-            android:textSize="14sp" />
-
+            android:textColor="?androidprv:attr/materialColorOnPrimary"
+            android:textAllCaps="false"
+            android:textSize="14sp"
+            android:textFontWeight="500"
+            android:fontFamily="google-sans" />
 </RelativeLayout>
diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml
index f58c4b0..c0916bc 100644
--- a/core/res/res/values-land/dimens.xml
+++ b/core/res/res/values-land/dimens.xml
@@ -69,7 +69,7 @@
     <dimen name="timepicker_left_side_width">250dip</dimen>
 
     <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
-    <dimen name="immersive_mode_cling_width">380dp</dimen>
+    <dimen name="immersive_mode_cling_width">500dp</dimen>
 
      <!-- Floating toolbar dimensions -->
      <dimen name="floating_toolbar_preferred_width">544dp</dimen>
diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index 4c70ea3..4aed94c 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -112,7 +112,7 @@
     <dimen name="keyguard_muliuser_selector_margin">12dp</dimen>
 
     <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
-    <dimen name="immersive_mode_cling_width">380dp</dimen>
+    <dimen name="immersive_mode_cling_width">600dp</dimen>
 
     <dimen name="floating_toolbar_preferred_width">544dp</dimen>
 
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 27b756d..f94c8ab 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -851,7 +851,12 @@
          of the screen.
          <p>This attribute is supported by the <a
             href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a>
-            element. -->
+            element.
+         <aside class="note"><b>Note:</b> Device manufacturers can configure devices to override
+            (ignore) this attribute to improve the layout of apps. See
+            <a href="{@docRoot}guide/practices/device-compatibility-mode">
+            Device compatibility mode</a>.
+         </aside> -->
     <attr name="screenOrientation">
         <!-- No preference specified: let the system decide the best
              orientation.  This will either be the orientation selected
@@ -1447,13 +1452,23 @@
          no other apps in multi-window visible on screen (e.g. picture-in-picture) or on other
          displays. Therefore, this flag cannot be used to assure an exclusive resource access.
 
-         <p>NOTE: A task's root activity value is applied to all additional activities launched in
+         <p>A task's root activity value is applied to all additional activities launched in
          the task. That is if the root activity of a task is resizeable then the system will treat
          all other activities in the task as resizeable and will not if the root activity isn't
          resizeable.
 
-         <p>NOTE: The value of {@link android.R.attr#screenOrientation} is ignored for
-         resizeable activities when in multi-window mode before Android 12. -->
+         <aside class="note"><b>Note:</b>
+            <ul>
+                <li>On Android 11 (API level 30) and lower, the value of
+                    {@link android.R.attr#screenOrientation} is ignored for resizeable activities
+                    in multi-window mode.
+                <li>Device manufacturers can configure devices to override (ignore) this attribute
+                    to force apps to resize. The override does not affect the app's support for
+                    multi-window mode. See
+                    <a href="{@docRoot}guide/practices/device-compatibility-mode">
+                    Device compatibility mode</a>.
+            </ul>
+         </aside> -->
     <attr name="resizeableActivity" format="boolean" />
 
     <!-- Indicates that the activity specifically supports the picture-in-picture form of
@@ -1470,27 +1485,37 @@
     <!-- This value indicates the maximum aspect ratio the activity supports. If the app runs on a
          device with a wider aspect ratio, the system automatically letterboxes the app, leaving
          portions of the screen unused so the app can run at its specified maximum aspect ratio.
-         <p>
-         Maximum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal
+         <p>Maximum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal
          form. For example, if the maximum aspect ratio is 7:3, set value to 2.33.
-         <p>
-         Value needs to be greater or equal to 1.0, otherwise it is ignored.
-         <p>
-         NOTE: This attribute is ignored if the activity has
-         {@link android.R.attr#resizeableActivity} set to true. -->
+         <p>Value needs to be greater or equal to 1.0, otherwise it is ignored.
+         <aside class="note"><b>Note:</b>
+            <ul>
+                <li>This attribute is ignored if the activity has
+                    {@link android.R.attr#resizeableActivity} set to {@code true}.
+                <li>Device manufacturers can configure devices to override (ignore) this attribute
+                    to improve the layout of apps. See
+                    <a href="{@docRoot}guide/practices/device-compatibility-mode">
+                    Device compatibility mode</a>.
+            </ul>
+         </aside> -->
     <attr name="maxAspectRatio" format="float" />
 
     <!-- This value indicates the minimum aspect ratio the activity supports. If the app runs on a
          device with a narrower aspect ratio, the system automatically letterboxes the app, leaving
          portions of the screen unused so the app can run at its specified minimum aspect ratio.
-         <p>
-         Minimum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal
-         form. For example, if the minimum aspect ratio is 4:3, set value to 1.33.
-         <p>
-         Value needs to be greater or equal to 1.0, otherwise it is ignored.
-         <p>
-         NOTE: This attribute is ignored if the activity has
-         {@link android.R.attr#resizeableActivity} set to true. -->
+         <p>Minimum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal
+            form. For example, if the minimum aspect ratio is 4:3, set value to 1.33.
+         <p>Value needs to be greater or equal to 1.0, otherwise it is ignored.
+         <aside class="note"><b>Note:</b>
+            <ul>
+                <li>This attribute is ignored if the activity has
+                    {@link android.R.attr#resizeableActivity} set to {@code true}.
+                <li>Device manufacturers can configure devices to override (ignore) this attribute
+                    to improve the layout of apps. See
+                    <a href="{@docRoot}guide/practices/device-compatibility-mode">
+                    Device compatibility mode</a>.
+            </ul>
+         </aside> -->
     <attr name="minAspectRatio" format="float" />
 
     <!-- This value indicates how tasks rooted at this activity will behave in lockTask mode.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b547a7a..b6e8383 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5125,7 +5125,7 @@
     <string name="immersive_cling_title">Viewing full screen</string>
 
     <!-- Cling help message description when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
-    <string name="immersive_cling_description">To exit, swipe down from the top.</string>
+    <string name="immersive_cling_description">To exit, swipe down from the top of your screen</string>
 
     <!-- Cling help message confirmation button when hiding the navigation bar entering immersive mode [CHAR LIMIT=30] -->
     <string name="immersive_cling_positive">Got it</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 639b746..c16bd24 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1619,6 +1619,7 @@
   <java-symbol type="layout" name="restrictions_pin_challenge" />
   <java-symbol type="layout" name="restrictions_pin_setup" />
   <java-symbol type="layout" name="immersive_mode_cling" />
+  <java-symbol type="id" name="immersive_cling_icon" />
   <java-symbol type="layout" name="user_switching_dialog" />
   <java-symbol type="layout" name="common_tab_settings" />
   <java-symbol type="layout" name="resolver_list_per_profile" />
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml
index 6e9d4db..94bde68 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml
@@ -25,10 +25,11 @@
 
     <application
         android:theme="@style/Theme"
+        android:icon="@mipmap/ic_launcher"
+        android:roundIcon="@mipmap/ic_launcher_round"
         android:label="Battery Stats Viewer">
         <activity android:name=".BatteryConsumerPickerActivity"
                   android:label="Battery Stats"
-                  android:icon="@mipmap/ic_launcher"
                   android:launchMode="singleTop"
                   android:exported="true">
             <intent-filter>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/ic_launcher_background.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/ic_launcher_background.xml
deleted file mode 100644
index 07d5da9..0000000
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/ic_launcher_background.xml
+++ /dev/null
@@ -1,170 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportWidth="108"
-    android:viewportHeight="108">
-    <path
-        android:fillColor="#3DDC84"
-        android:pathData="M0,0h108v108h-108z" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M9,0L9,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,0L19,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,0L29,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,0L39,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,0L49,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,0L59,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,0L69,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,0L79,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M89,0L89,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M99,0L99,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,9L108,9"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,19L108,19"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,29L108,29"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,39L108,39"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,49L108,49"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,59L108,59"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,69L108,69"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,79L108,79"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,89L108,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,99L108,99"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,29L89,29"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,39L89,39"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,49L89,49"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,59L89,59"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,69L89,69"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,79L89,79"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,19L29,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,19L39,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,19L49,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,19L59,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,19L69,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,19L79,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-</vector>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/ic_launcher_foreground.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/ic_launcher_foreground.xml
deleted file mode 100644
index fc0c6ab..0000000
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportWidth="108"
-    android:viewportHeight="108">
-    <path
-        android:fillType="evenOdd"
-        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,
-        49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
-        android:strokeWidth="1"
-        android:strokeColor="#00000000">
-        <aapt:attr name="android:fillColor">
-            <gradient
-                android:endX="78.5885"
-                android:endY="90.9159"
-                android:startX="48.7653"
-                android:startY="61.0927"
-                android:type="linear">
-                <item
-                    android:color="#44000000"
-                    android:offset="0.0" />
-                <item
-                    android:color="#00000000"
-                    android:offset="1.0" />
-            </gradient>
-        </aapt:attr>
-    </path>
-    <path
-        android:fillColor="#FFFFFF"
-        android:fillType="nonZero"
-        android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,
-        50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,
-        37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,
-        42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,
-        40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,
-        52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,
-        56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,
-        52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
-        android:strokeWidth="1"
-        android:strokeColor="#00000000" />
-</vector>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml
index f35a210..987de6b 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml
@@ -17,6 +17,7 @@
 <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/swipe_refresh"
+    android:paddingTop="?attr/actionBarSize"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
index cf50d2a..2d276a5 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
@@ -17,11 +17,13 @@
 <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/swipe_refresh"
+    android:paddingTop="?attr/actionBarSize"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
     <LinearLayout
         android:orientation="vertical"
+        android:paddingTop="?attr/actionBarSize"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
 
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi-v26/ic_launcher.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi-v26/ic_launcher_round.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi/ic_launcher.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi/ic_launcher.xml
index 6b78462..036d09b 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi/ic_launcher.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi/ic_launcher.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi/ic_launcher_round.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..0057985
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher_foreground.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..085df9d
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher_round.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..bcb3b7d
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..3d1cf0e
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher_foreground.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..bfd4568
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher_round.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..4cf0d43
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..ac4f693
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher_foreground.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..cc6b763
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher_round.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1f17221
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..b70e145
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..6e46bce
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher_round.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..3fa346c
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..8b463f2
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..849caff
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher_round.webp b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..bd6e312
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/ic_launcher_background.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..1e90e07
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="ic_launcher_background">#1A7945</color>
+</resources>
\ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml
index 629d729..fa30b2c 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml
@@ -21,6 +21,7 @@
         <item name="colorPrimary">#34a853</item>
         <item name="android:windowActionBar">true</item>
         <item name="android:windowNoTitle">false</item>
+        <item name="android:windowDrawsSystemBarBackgrounds">false</item>
     </style>
 
     <style name="LoadTestCardView" parent="Widget.MaterialComponents.CardView">
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 15c9047..c8ea374 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -34,6 +34,9 @@
 import android.appwidget.AppWidgetHostView;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -834,33 +837,6 @@
     }
 
     @Test
-    public void visitUris_intents() {
-        RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
-
-        Uri fillIntentUri = Uri.parse("content://intent/fill");
-        views.setOnCheckedChangeResponse(
-                R.id.layout,
-                RemoteViews.RemoteResponse.fromFillInIntent(new Intent("action", fillIntentUri)));
-
-        Uri pendingIntentUri = Uri.parse("content://intent/pending");
-        PendingIntent pendingIntent = getPendingIntentWithUri(pendingIntentUri);
-        views.setOnClickResponse(
-                R.id.layout,
-                RemoteViews.RemoteResponse.fromPendingIntent(pendingIntent));
-
-        Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
-        views.visitUris(visitor);
-        verify(visitor, times(1)).accept(eq(fillIntentUri));
-        verify(visitor, times(1)).accept(eq(pendingIntentUri));
-    }
-
-    private PendingIntent getPendingIntentWithUri(Uri uri) {
-        return PendingIntent.getActivity(mContext, 0,
-                new Intent("action", uri),
-                PendingIntent.FLAG_IMMUTABLE);
-    }
-
-    @Test
     public void layoutInflaterFactory_nothingSet_returnsNull() {
         final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test);
         assertNull(rv.getLayoutInflaterFactory());
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 82d2381..5d4139e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -23,23 +23,19 @@
     <!-- Needed for Build.getSerial(), which is used to send a unique number for serial, per HUIG. -->
     <privapp-permissions package="android.car.usb.handler">
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.angle">
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.apps.tag">
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.backupconfirm">
         <permission name="android.permission.BACKUP"/>
         <permission name="android.permission.CRYPT_KEEPER"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.credentialmanager">
@@ -50,13 +46,11 @@
         <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
         <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.imsserviceentitlement">
         <permission name="android.permission.MODIFY_PHONE_STATE" />
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.launcher3">
@@ -68,7 +62,6 @@
         <permission name="android.permission.INSTALL_LOCATION_PROVIDER"/>
         <permission name="android.permission.UPDATE_DEVICE_STATS"/>
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.managedprovisioning">
@@ -98,7 +91,6 @@
         <permission name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"/>
         <permission name="android.permission.BIND_CARRIER_SERVICES"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.mtp">
@@ -108,19 +100,16 @@
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
         <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.musicfx">
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.networkrecommendation">
         <permission name="android.permission.SCORE_NETWORKS"/>
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.packageinstaller">
@@ -201,7 +190,6 @@
         <permission name="android.permission.USE_RESERVED_DISK"/>
         <permission name="android.permission.LOG_COMPAT_CHANGE" />
         <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.providers.contacts">
@@ -215,7 +203,6 @@
         <permission name="android.permission.USE_RESERVED_DISK"/>
         <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
         <permission name="android.permission.LOG_COMPAT_CHANGE" />
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.providers.downloads">
@@ -228,7 +215,6 @@
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
         <permission name="android.permission.UPDATE_DEVICE_STATS"/>
         <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.providers.telephony">
@@ -238,7 +224,6 @@
         <!-- Permissions required for reading and logging compat changes -->
         <permission name="android.permission.LOG_COMPAT_CHANGE" />
         <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.server.telecom">
@@ -254,13 +239,11 @@
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.STOP_APP_SWITCHES"/>
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.sharedstoragebackup">
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
         <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.shell">
@@ -602,12 +585,10 @@
         <permission name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/>
         <permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.soundpicker">
         <permission name="android.permission.INTERACT_ACROSS_USERS" />
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.tv">
@@ -619,18 +600,15 @@
         <permission name="android.permission.READ_CONTENT_RATING_SYSTEMS"/>
         <permission name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"/>
         <permission name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.vpndialogs">
         <permission name="android.permission.CONTROL_VPN"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.wallpaper.livepicker">
         <permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
         <permission name="android.permission.BIND_WALLPAPER"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.wallpaper">
@@ -638,15 +616,14 @@
         <permission name="android.permission.BIND_WALLPAPER"/>
         <permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
         <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.dynsystem">
         <permission name="android.permission.REBOOT"/>
         <permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/>
         <permission name="android.permission.READ_OEM_UNLOCK_STATE"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
+
     <privapp-permissions package="com.android.settings">
         <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
         <permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/>
@@ -657,12 +634,10 @@
 
     <privapp-permissions package="com.android.bips">
         <permission name="android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.calllogbackup">
         <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
-        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
    <privapp-permissions package="com.android.devicediagnostics">
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 5825fac..eea5690 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -679,6 +679,9 @@
         sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE,
                 new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
                         ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION));
+        sErrorCodeToFailureInfo.put(ResponseCode.GET_ATTESTATION_APPLICATION_ID_FAILED,
+                new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
+                        ERROR_INTERNAL_SYSTEM_ERROR));
         sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY,
                 new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
                         ERROR_ATTESTATION_KEYS_UNAVAILABLE));
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 15f8c32..112eb61 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -111,3 +111,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "animate_bubble_size_change"
+    namespace: "multitasking"
+    description: "Turns on the animation for bubble bar icons size change"
+    bug: "335575529"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index f27f46c..89cddc3 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -459,6 +459,12 @@
          start of this area. -->
     <dimen name="desktop_mode_customizable_caption_margin_end">152dp</dimen>
 
+    <!-- The default minimum allowed window width when resizing a window in desktop mode. -->
+    <dimen name="desktop_mode_minimum_window_width">386dp</dimen>
+
+    <!-- The default minimum allowed window height when resizing a window in desktop mode. -->
+    <dimen name="desktop_mode_minimum_window_height">352dp</dimen>
+
     <!-- The width of the maximize menu in desktop mode. -->
     <dimen name="desktop_mode_maximize_menu_width">228dp</dimen>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 82c399a..fe1c9c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -22,12 +22,16 @@
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED;
 
+import android.content.Context;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
 import android.view.SurfaceControl;
 
+import com.android.window.flags.Flags;
+import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.shared.DesktopModeStatus;
 
 /**
  * Utility class that contains logic common to classes implementing {@link DragPositioningCallback}
@@ -35,11 +39,11 @@
  * and applying that change to the task bounds when applicable.
  */
 public class DragPositioningCallbackUtility {
-
     /**
      * Determine the delta between input's current point and the input start point.
-     * @param inputX current input x coordinate
-     * @param inputY current input y coordinate
+     *
+     * @param inputX               current input x coordinate
+     * @param inputY               current input y coordinate
      * @param repositionStartPoint initial input coordinate
      * @return delta between these two points
      */
@@ -52,13 +56,14 @@
     /**
      * Based on type of resize and delta provided, calculate the new bounds to display for this
      * task.
-     * @param ctrlType type of drag being performed
-     * @param repositionTaskBounds the bounds the task is being repositioned to
+     *
+     * @param ctrlType              type of drag being performed
+     * @param repositionTaskBounds  the bounds the task is being repositioned to
      * @param taskBoundsAtDragStart the bounds of the task on the first drag input event
-     * @param stableBounds bounds that represent the resize limit of this task
-     * @param delta difference between start input and current input in x/y coordinates
-     * @param displayController task's display controller
-     * @param windowDecoration window decoration of the task being dragged
+     * @param stableBounds          bounds that represent the resize limit of this task
+     * @param delta                 difference between start input and current input in x/y
+     *                              coordinates
+     * @param windowDecoration      window decoration of the task being dragged
      * @return whether this method changed repositionTaskBounds
      */
     static boolean changeBounds(int ctrlType, Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
@@ -142,8 +147,9 @@
     /**
      * If task bounds are outside of provided drag area, snap the bounds to be just inside the
      * drag area.
+     *
      * @param repositionTaskBounds bounds determined by task positioner
-     * @param validDragArea the area that task must be positioned inside
+     * @param validDragArea        the area that task must be positioned inside
      * @return whether bounds were modified
      */
     public static boolean snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
@@ -170,18 +176,38 @@
 
     private static float getMinWidth(DisplayController displayController,
             WindowDecoration windowDecoration) {
-        return windowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinSize(displayController,
+        return windowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinWidth(displayController,
                 windowDecoration)
                 : windowDecoration.mTaskInfo.minWidth;
     }
 
     private static float getMinHeight(DisplayController displayController,
             WindowDecoration windowDecoration) {
-        return windowDecoration.mTaskInfo.minHeight < 0 ? getDefaultMinSize(displayController,
+        return windowDecoration.mTaskInfo.minHeight < 0 ? getDefaultMinHeight(displayController,
                 windowDecoration)
                 : windowDecoration.mTaskInfo.minHeight;
     }
 
+    private static float getDefaultMinWidth(DisplayController displayController,
+            WindowDecoration windowDecoration) {
+        if (isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)) {
+            return WindowDecoration.loadDimensionPixelSize(
+                    windowDecoration.mDecorWindowContext.getResources(),
+                    R.dimen.desktop_mode_minimum_window_width);
+        }
+        return getDefaultMinSize(displayController, windowDecoration);
+    }
+
+    private static float getDefaultMinHeight(DisplayController displayController,
+            WindowDecoration windowDecoration) {
+        if (isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)) {
+            return WindowDecoration.loadDimensionPixelSize(
+                    windowDecoration.mDecorWindowContext.getResources(),
+                    R.dimen.desktop_mode_minimum_window_height);
+        }
+        return getDefaultMinSize(displayController, windowDecoration);
+    }
+
     private static float getDefaultMinSize(DisplayController displayController,
             WindowDecoration windowDecoration) {
         float density =  displayController.getDisplayLayout(windowDecoration.mTaskInfo.displayId)
@@ -189,9 +215,15 @@
         return windowDecoration.mTaskInfo.defaultMinSize * density;
     }
 
+    private static boolean isSizeConstraintForDesktopModeEnabled(Context context) {
+        return DesktopModeStatus.canEnterDesktopMode(context)
+                && Flags.enableDesktopWindowingSizeConstraints();
+    }
+
     interface DragStartListener {
         /**
          * Inform the implementing class that a drag resize has started
+         *
          * @param taskId id of this positioner's {@link WindowDecoration}
          */
         void onDragStart(int taskId);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index e6fabcf..f750e6b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -16,14 +16,20 @@
 package com.android.wm.shell.windowdecor
 
 import android.app.ActivityManager
+import android.content.Context
+import android.content.res.Resources
 import android.graphics.PointF
 import android.graphics.Rect
 import android.os.IBinder
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.view.Display
 import android.window.WindowContainerToken
+import com.android.window.flags.Flags
+import com.android.wm.shell.R
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.shared.DesktopModeStatus
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
@@ -33,8 +39,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.Mockito.any
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 /**
@@ -57,6 +63,10 @@
     private lateinit var mockDisplayLayout: DisplayLayout
     @Mock
     private lateinit var mockDisplay: Display
+    @Mock
+    private lateinit var mockContext: Context
+    @Mock
+    private lateinit var mockResources: Resources
 
     @Before
     fun setup() {
@@ -69,16 +79,15 @@
             (i.arguments.first() as Rect).set(STABLE_BOUNDS)
         }
 
-        mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
-            taskId = TASK_ID
-            token = taskToken
-            minWidth = MIN_WIDTH
-            minHeight = MIN_HEIGHT
-            defaultMinSize = DEFAULT_MIN
-            displayId = DISPLAY_ID
-            configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
-        }
+        initializeTaskInfo()
         mockWindowDecoration.mDisplay = mockDisplay
+        mockWindowDecoration.mDecorWindowContext = mockContext
+        whenever(mockContext.getResources()).thenReturn(mockResources)
+        whenever(mockWindowDecoration.mDecorWindowContext.resources).thenReturn(mockResources)
+        whenever(mockResources.getDimensionPixelSize(R.dimen.desktop_mode_minimum_window_width))
+                .thenReturn(DESKTOP_MODE_MIN_WIDTH)
+        whenever(mockResources.getDimensionPixelSize(R.dimen.desktop_mode_minimum_window_height))
+                .thenReturn(DESKTOP_MODE_MIN_HEIGHT)
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
     }
 
@@ -93,8 +102,8 @@
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
         DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
-                repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
-                mockDisplayController, mockWindowDecoration)
+            repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+            mockWindowDecoration)
 
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
@@ -113,8 +122,8 @@
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
         DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
-                repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
-                mockDisplayController, mockWindowDecoration)
+            repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+            mockWindowDecoration)
 
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top + 5)
@@ -127,14 +136,14 @@
         val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
         val repositionTaskBounds = Rect(STARTING_BOUNDS)
 
-        // Resize to width of 95px and width of -5px with minimum of 10px
+        // Resize to width of 95px and height of -5px with minimum of 10px
         val newX = STARTING_BOUNDS.right.toFloat() - 5
         val newY = STARTING_BOUNDS.top.toFloat() + 105
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
         DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
-                repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
-                mockDisplayController, mockWindowDecoration)
+            repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+            mockWindowDecoration)
 
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
@@ -153,8 +162,8 @@
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
         DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
-                repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
-                mockDisplayController, mockWindowDecoration)
+            repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+            mockWindowDecoration)
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top + 80)
         assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 80)
@@ -172,8 +181,8 @@
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
         DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
-                repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
-            mockDisplayController, mockWindowDecoration)
+            repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+            mockWindowDecoration)
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
         assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
@@ -196,14 +205,13 @@
         assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
         assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
         assertThat(repositionTaskBounds.right)
-            .isEqualTo(validDragArea.left + STARTING_BOUNDS.width())
+                .isEqualTo(validDragArea.left + STARTING_BOUNDS.width())
         assertThat(repositionTaskBounds.bottom)
-            .isEqualTo(validDragArea.bottom + STARTING_BOUNDS.height())
+                .isEqualTo(validDragArea.bottom + STARTING_BOUNDS.height())
     }
 
     @Test
     fun testChangeBounds_toDisallowedBounds_freezesAtLimit() {
-        var hasMoved = false
         val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(),
             STARTING_BOUNDS.bottom.toFloat())
         val repositionTaskBounds = Rect(STARTING_BOUNDS)
@@ -212,26 +220,127 @@
         var newY = STARTING_BOUNDS.bottom.toFloat() + 10
         var delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
         assertTrue(DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
-                repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
-                mockDisplayController, mockWindowDecoration))
-        hasMoved = true
+            repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+            mockWindowDecoration))
         // Resize width to 120px, height to disallowed area which should not result in a change.
         newX += 10
         newY = DISALLOWED_RESIZE_AREA.top.toFloat()
         delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
         assertTrue(DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
-            repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
-            mockDisplayController, mockWindowDecoration))
+            repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+            mockWindowDecoration))
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
         assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right + 20)
         assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom + 10)
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
+    fun taskMinWidthHeightUndefined_changeBoundsInDesktopModeLessThanMin_shouldNotChangeBounds() {
+        whenever(DesktopModeStatus.canEnterDesktopMode(mockContext)).thenReturn(true)
+        initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1)
+        val startingPoint =
+            PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat())
+        val repositionTaskBounds = Rect(STARTING_BOUNDS)
+        // Shrink height and width to 1px. The default allowed width and height are defined in
+        // R.dimen.desktop_mode_minimum_window_width and R.dimen.desktop_mode_minimum_window_height
+        val newX = STARTING_BOUNDS.right.toFloat() - 99
+        val newY = STARTING_BOUNDS.bottom.toFloat() - 99
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+            repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+            mockWindowDecoration)
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
+    fun taskMinWidthHeightUndefined_changeBoundsInDesktopModeAllowedSize_shouldChangeBounds() {
+        whenever(DesktopModeStatus.canEnterDesktopMode(mockContext)).thenReturn(true)
+        initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1)
+        val startingPoint =
+            PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat())
+        val repositionTaskBounds = Rect(STARTING_BOUNDS)
+        // Shrink height and width to 20px. The default allowed width and height are defined in
+        // R.dimen.desktop_mode_minimum_window_width and R.dimen.desktop_mode_minimum_window_height
+        val newX = STARTING_BOUNDS.right.toFloat() - 80
+        val newY = STARTING_BOUNDS.bottom.toFloat() - 80
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+            repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+            mockWindowDecoration)
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 80)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom - 80)
+    }
+
+    @Test
+    fun taskMinWidthHeightUndefined_changeBoundsLessThanDefaultMinSize_shouldNotChangeBounds() {
+        initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1)
+        val startingPoint =
+            PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat())
+        val repositionTaskBounds = Rect(STARTING_BOUNDS)
+        // Shrink height and width to 1px. The default allowed width and height are defined in the
+        // defaultMinSize of the TaskInfo.
+        val newX = STARTING_BOUNDS.right.toFloat() - 99
+        val newY = STARTING_BOUNDS.bottom.toFloat() - 99
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+            repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+            mockWindowDecoration)
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+    @Test
+    fun taskMinWidthHeightUndefined_changeBoundsToAnAllowedSize_shouldChangeBounds() {
+        initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1)
+        val startingPoint =
+            PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat())
+        val repositionTaskBounds = Rect(STARTING_BOUNDS)
+        // Shrink height and width to 50px. The default allowed width and height are defined in the
+        // defaultMinSize of the TaskInfo.
+        val newX = STARTING_BOUNDS.right.toFloat() - 50
+        val newY = STARTING_BOUNDS.bottom.toFloat() - 50
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+            repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+            mockWindowDecoration)
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 50)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom - 50)
+    }
+
+    private fun initializeTaskInfo(taskMinWidth: Int = MIN_WIDTH, taskMinHeight: Int = MIN_HEIGHT) {
+        mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+            taskId = TASK_ID
+            token = taskToken
+            minWidth = taskMinWidth
+            minHeight = taskMinHeight
+            defaultMinSize = DEFAULT_MIN
+            displayId = DISPLAY_ID
+            configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+        }
+    }
+
     companion object {
         private const val TASK_ID = 5
         private const val MIN_WIDTH = 10
         private const val MIN_HEIGHT = 10
+        private const val DESKTOP_MODE_MIN_WIDTH = 20
+        private const val DESKTOP_MODE_MIN_HEIGHT = 20
         private const val DENSITY_DPI = 20
         private const val DEFAULT_MIN = 40
         private const val DISPLAY_ID = 1
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 9174556..6667504 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -2,6 +2,8 @@
 
 import android.app.ActivityManager
 import android.app.WindowConfiguration
+import android.content.Context
+import android.content.res.Resources
 import android.graphics.Point
 import android.graphics.Rect
 import android.os.IBinder
@@ -17,6 +19,7 @@
 import android.window.WindowContainerTransaction
 import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
 import androidx.test.filters.SmallTest
+import com.android.wm.shell.R
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.common.DisplayController
@@ -83,7 +86,10 @@
     private lateinit var mockTransaction: SurfaceControl.Transaction
     @Mock
     private lateinit var mockTransitionBinder: IBinder
-
+    @Mock
+    private lateinit var mockContext: Context
+    @Mock
+    private lateinit var mockResources: Resources
     private lateinit var taskPositioner: FluidResizeTaskPositioner
 
     @Before
@@ -119,6 +125,12 @@
         }
         `when`(mockWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
         mockWindowDecoration.mDisplay = mockDisplay
+        mockWindowDecoration.mDecorWindowContext = mockContext
+        whenever(mockWindowDecoration.mDecorWindowContext.resources).thenReturn(mockResources)
+        whenever(mockResources.getDimensionPixelSize(R.dimen.desktop_mode_minimum_window_width))
+                .thenReturn(DESKTOP_MODE_MIN_WIDTH)
+        whenever(mockResources.getDimensionPixelSize(R.dimen.desktop_mode_minimum_window_height))
+                .thenReturn(DESKTOP_MODE_MIN_HEIGHT)
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
         whenever(mockTransitions.startTransition(anyInt(), any(), any()))
                 .doReturn(mockTransitionBinder)
@@ -788,6 +800,8 @@
         private const val TASK_ID = 5
         private const val MIN_WIDTH = 10
         private const val MIN_HEIGHT = 10
+        private const val DESKTOP_MODE_MIN_WIDTH = 20
+        private const val DESKTOP_MODE_MIN_HEIGHT = 20
         private const val DENSITY_DPI = 20
         private const val DEFAULT_MIN = 40
         private const val DISPLAY_ID = 1
diff --git a/packages/CrashRecovery/aconfig/flags.aconfig b/packages/CrashRecovery/aconfig/flags.aconfig
index 8cdef38..80412321 100644
--- a/packages/CrashRecovery/aconfig/flags.aconfig
+++ b/packages/CrashRecovery/aconfig/flags.aconfig
@@ -12,15 +12,22 @@
 flag {
     name: "enable_crashrecovery"
     is_exported: true
-    namespace: "crashrecovery"
+    namespace: "modularization"
     description: "Enables various dependencies of crashrecovery module"
     bug: "289203818"
 }
 
 flag {
     name: "allow_rescue_party_flag_resets"
-    namespace: "crashrecovery"
+    namespace: "modularization"
     description: "Enables rescue party flag resets"
     bug: "287618292"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "reenable_settings_resets"
+    namespace: "modularization"
+    description: "Re-enables settings resets only, deletes flag resets"
+    bug: "333847376"
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index c35721c..772e02e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -34,6 +34,7 @@
 import com.android.credentialmanager.getflow.RequestDisplayInfo
 import com.android.credentialmanager.getflow.generateDisplayTitleTextResCode
 import com.android.credentialmanager.model.BiometricRequestInfo
+import com.android.credentialmanager.model.CredentialType
 import com.android.credentialmanager.model.EntryInfo
 import com.android.credentialmanager.model.creation.CreateOptionInfo
 import com.android.credentialmanager.model.get.CredentialEntryInfo
@@ -476,7 +477,9 @@
         return null
     }
     val singleEntryType = selectedEntry.credentialType
-    val username = selectedEntry.userName
+    val descriptionName = if (singleEntryType == CredentialType.PASSKEY &&
+        !selectedEntry.displayName.isNullOrBlank()) selectedEntry.displayName else
+        selectedEntry.userName
 
     // TODO(b/336362538) : In W, utilize updated localization strings
     displayTitleText = context.getString(
@@ -487,7 +490,7 @@
     descriptionText = context.getString(
         R.string.get_dialog_description_single_tap,
         getRequestDisplayInfo.appName,
-        username
+        descriptionName
     )
 
     return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index f65a1b7..c48e7e4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -71,7 +71,7 @@
                 },
                 scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = .32f),
                 shape = EntryShape.TopRoundedCorner,
-                windowInsets = WindowInsets.navigationBars,
+                contentWindowInsets = { WindowInsets.navigationBars },
                 dragHandle = null,
                 // Never take over the full screen. We always want to leave some top scrim space
                 // for exiting and viewing the underlying app to help a user gain context.
diff --git a/packages/CtsShim/apk/riscv64/CtsShim.apk b/packages/CtsShim/apk/riscv64/CtsShim.apk
index 5ab190d..af306a5 100644
--- a/packages/CtsShim/apk/riscv64/CtsShim.apk
+++ b/packages/CtsShim/apk/riscv64/CtsShim.apk
Binary files differ
diff --git a/packages/CtsShim/apk/riscv64/CtsShimPriv.apk b/packages/CtsShim/apk/riscv64/CtsShimPriv.apk
index 441f86f..9a9997d 100644
--- a/packages/CtsShim/apk/riscv64/CtsShimPriv.apk
+++ b/packages/CtsShim/apk/riscv64/CtsShimPriv.apk
Binary files differ
diff --git a/packages/InputDevices/res/raw/keyboard_layout_serbian_and_montenegrin_cyrillic.kcm b/packages/InputDevices/res/raw/keyboard_layout_serbian_and_montenegrin_cyrillic.kcm
new file mode 100644
index 0000000..6fa54f9
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_serbian_and_montenegrin_cyrillic.kcm
@@ -0,0 +1,320 @@
+# Copyright 2024 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.
+
+#
+# Serbian and Montenegrin (Cyrillic) keyboard layout.
+#
+
+type OVERLAY
+
+map key 86 PLUS
+
+### ROW 1
+
+key GRAVE {
+    label:                              '`'
+    base:                               '`'
+    shift:                              '~'
+}
+
+key 1 {
+    label:                              '1'
+    base:                               '1'
+    shift:                              '!'
+}
+
+key 2 {
+    label:                              '2'
+    base:                               '2'
+    shift:                              '\u0022'
+}
+
+key 3 {
+    label:                              '3'
+    base:                               '3'
+    shift:                              '#'
+}
+
+key 4 {
+    label:                              '4'
+    base:                               '4'
+    shift:                              '$'
+}
+
+key 5 {
+    label:                              '5'
+    base:                               '5'
+    shift:                              '%'
+}
+
+key 6 {
+    label:                              '6'
+    base:                               '6'
+    shift:                              '&'
+}
+
+key 7 {
+    label:                              '7'
+    base:                               '7'
+    shift:                              '/'
+}
+
+key 8 {
+    label:                              '8'
+    base:                               '8'
+    shift:                              '('
+}
+
+key 9 {
+    label:                              '9'
+    base:                               '9'
+    shift:                              ')'
+}
+
+key 0 {
+    label:                              '0'
+    base:                               '0'
+    shift:                              '='
+}
+
+key MINUS {
+    label:                              '\''
+    base:                               '\u030d'
+    shift:                              '?'
+}
+
+key EQUALS {
+    label:                              '+'
+    base:                               '+'
+    shift:                              '*'
+}
+
+### ROW 2
+
+key Q {
+    label:                              '\u0409'
+    base, capslock+shift:               '\u0459'
+    shift, capslock:                    '\u0409'
+}
+
+key W {
+    label:                              '\u040a'
+    base, capslock+shift:               '\u045a'
+    shift, capslock:                    '\u040a'
+}
+
+key E {
+    label:                              '\u0415'
+    base, capslock+shift:               '\u0435'
+    shift, capslock:                    '\u0415'
+    ralt:                               '\u20ac'
+}
+
+key R {
+    label:                              '\u0420'
+    base, capslock+shift:               '\u0440'
+    shift, capslock:                    '\u0420'
+}
+
+key T {
+    label:                              '\u0422'
+    base, capslock+shift:               '\u0442'
+    shift, capslock:                    '\u0422'
+}
+
+key Y {
+    label:                              '\u0417'
+    base, capslock+shift:               '\u0437'
+    shift, capslock:                    '\u0417'
+}
+
+key U {
+    label:                              '\u0423'
+    base, capslock+shift:               '\u0443'
+    shift, capslock:                    '\u0423'
+}
+
+key I {
+    label:                              '\u0418'
+    base, capslock+shift:               '\u0438'
+    shift, capslock:                    '\u0418'
+}
+
+key O {
+    label:                              '\u041e'
+    base, capslock+shift:               '\u043e'
+    shift, capslock:                    '\u041e'
+}
+
+key P {
+    label:                              '\u041f'
+    base, capslock+shift:               '\u043f'
+    shift, capslock:                    '\u041f'
+}
+
+key LEFT_BRACKET {
+    label:                              '\u0428'
+    base, capslock+shift:               '\u0448'
+    shift, capslock:                    '\u0428'
+}
+
+key RIGHT_BRACKET {
+    label:                              '\u0402'
+    base, capslock+shift:               '\u0452'
+    shift, capslock:                    '\u0402'
+}
+
+### ROW 3
+
+key A {
+    label:                              '\u0410'
+    base, capslock+shift:               '\u0430'
+    shift, capslock:                    '\u0410'
+}
+
+key S {
+    label:                              '\u0421'
+    base, capslock+shift:               '\u0441'
+    shift, capslock:                    '\u0421'
+}
+
+key D {
+    label:                              '\u0414'
+    base, capslock+shift:               '\u0434'
+    shift, capslock:                    '\u0414'
+}
+
+key F {
+    label:                              '\u0424'
+    base, capslock+shift:               '\u0444'
+    shift, capslock:                    '\u0424'
+}
+
+key G {
+    label:                              '\u0413'
+    base, capslock+shift:               '\u0433'
+    shift, capslock:                    '\u0413'
+}
+
+key H {
+    label:                              '\u0425'
+    base, capslock+shift:               '\u0445'
+    shift, capslock:                    '\u0425'
+}
+
+key J {
+    label:                              '\u0408'
+    base, capslock+shift:               '\u0458'
+    shift, capslock:                    '\u0408'
+}
+
+key K {
+    label:                              '\u041a'
+    base, capslock+shift:               '\u043a'
+    shift, capslock:                    '\u041a'
+}
+
+key L {
+    label:                              '\u041b'
+    base, capslock+shift:               '\u043b'
+    shift, capslock:                    '\u041b'
+}
+
+key SEMICOLON {
+    label:                              '\u0427'
+    base, capslock+shift:               '\u0447'
+    shift, capslock:                    '\u0427'
+}
+
+key APOSTROPHE {
+    label:                              '\u040b'
+    base, capslock+shift:               '\u045b'
+    shift, capslock:                    '\u040b'
+}
+
+key BACKSLASH {
+    label:                              '\u0416'
+    base, capslock+shift:               '\u0436'
+    shift, capslock:                    '\u0416'
+}
+
+### ROW 4
+
+key PLUS {
+    label:                              '<'
+    base:                               '<'
+    shift:                              '>'
+}
+
+key Z {
+    label:                              '\u0405'
+    base, capslock+shift:               '\u0455'
+    shift, capslock:                    '\u0405'
+}
+
+key X {
+    label:                              '\u040f'
+    base, capslock+shift:               '\u045f'
+    shift, capslock:                    '\u040f'
+}
+
+key C {
+    label:                              '\u0426'
+    base, capslock+shift:               '\u0446'
+    shift, capslock:                    '\u0426'
+}
+
+key V {
+    label:                              '\u0412'
+    base, capslock+shift:               '\u0432'
+    shift, capslock:                    '\u0412'
+}
+
+key B {
+    label:                              '\u0411'
+    base, capslock+shift:               '\u0431'
+    shift, capslock:                    '\u0411'
+}
+
+key N {
+    label:                              '\u041d'
+    base, capslock+shift:               '\u043d'
+    shift, capslock:                    '\u041d'
+}
+
+key M {
+    label:                              '\u041c'
+    base, capslock+shift:               '\u043c'
+    shift, capslock:                    '\u041c'
+}
+
+key COMMA {
+    label:                              ','
+    base:                               ','
+    shift:                              ';'
+    ralt:                               '<'
+}
+
+key PERIOD {
+    label:                              '.'
+    base:                               '.'
+    shift:                              ':'
+    ralt:                               '>'
+}
+
+key SLASH {
+    label:                              '-'
+    base:                               '-'
+    shift:                              '_'
+}
\ No newline at end of file
diff --git a/packages/InputDevices/res/raw/keyboard_layout_serbian_latin.kcm b/packages/InputDevices/res/raw/keyboard_layout_serbian_and_montenegrin_latin.kcm
similarity index 99%
rename from packages/InputDevices/res/raw/keyboard_layout_serbian_latin.kcm
rename to packages/InputDevices/res/raw/keyboard_layout_serbian_and_montenegrin_latin.kcm
index 0ff1dea..8e4d7b1 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_serbian_latin.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_serbian_and_montenegrin_latin.kcm
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 #
-# Serbian (Latin) keyboard layout.
+# Serbian and Montenegrin (Latin) keyboard layout.
 #
 
 type OVERLAY
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index be7d2c1..5a91125 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -158,4 +158,10 @@
 
     <!-- Montenegrin (Latin) keyboard layout label. [CHAR LIMIT=35] -->
     <string name="keyboard_layout_montenegrin_latin">Montenegrin (Latin)</string>
+
+    <!-- Serbian (Cyrillic) keyboard layout label. [CHAR LIMIT=35] -->
+    <string name="keyboard_layout_serbian_cyrillic">Serbian (Cyrillic)</string>
+
+    <!-- Montenegrin (Cyrillic) keyboard layout label. [CHAR LIMIT=35] -->
+    <string name="keyboard_layout_montenegrin_cyrillic">Montenegrin (Cyrillic)</string>
 </resources>
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 84e4b9e..9309489 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -336,14 +336,28 @@
     <keyboard-layout
         android:name="keyboard_layout_serbian_latin"
         android:label="@string/keyboard_layout_serbian_latin"
-        android:keyboardLayout="@raw/keyboard_layout_serbian_latin"
+        android:keyboardLayout="@raw/keyboard_layout_serbian_and_montenegrin_latin"
         android:keyboardLocale="sr-Latn-RS"
         android:keyboardLayoutType="qwertz" />
 
     <keyboard-layout
         android:name="keyboard_layout_montenegrin_latin"
         android:label="@string/keyboard_layout_montenegrin_latin"
-        android:keyboardLayout="@raw/keyboard_layout_serbian_latin"
+        android:keyboardLayout="@raw/keyboard_layout_serbian_and_montenegrin_latin"
         android:keyboardLocale="cnr-Latn-ME"
         android:keyboardLayoutType="qwertz" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_serbian_cyrillic"
+        android:label="@string/keyboard_layout_serbian_cyrillic"
+        android:keyboardLayout="@raw/keyboard_layout_serbian_and_montenegrin_cyrillic"
+        android:keyboardLocale="sr-Cyrl-RS"
+        android:keyboardLayoutType="extended" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_montenegrin_cyrillic"
+        android:label="@string/keyboard_layout_montenegrin_cyrillic"
+        android:keyboardLayout="@raw/keyboard_layout_serbian_and_montenegrin_cyrillic"
+        android:keyboardLocale="cnr-Cyrl-ME"
+        android:keyboardLayoutType="extended" />
 </keyboard-layouts>
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 45667f5..232fa92 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.7.0-alpha08"
+    extra["jetpackComposeVersion"] = "1.7.0-SNAPSHOT"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/settings.gradle.kts b/packages/SettingsLib/Spa/settings.gradle.kts
index e003c81..31f462e 100644
--- a/packages/SettingsLib/Spa/settings.gradle.kts
+++ b/packages/SettingsLib/Spa/settings.gradle.kts
@@ -36,6 +36,9 @@
         }
         mavenCentral()
         maven {
+            url = uri("https://androidx.dev/snapshots/builds/11846308/artifacts/repository")
+        }
+        maven {
             url = uri("https://jitpack.io")
             content {
                 includeGroup("com.github.PhilJay")
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 4aa57b3..f98695e 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@
     api("androidx.slice:slice-builders:1.1.0-alpha02")
     api("androidx.slice:slice-core:1.1.0-alpha02")
     api("androidx.slice:slice-view:1.1.0-alpha02")
-    api("androidx.compose.material3:material3:1.3.0-alpha06")
+    api("androidx.compose.material3:material3:1.3.0-SNAPSHOT")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-livedata-ktx")
     api("androidx.lifecycle:lifecycle-runtime-compose")
-    api("androidx.navigation:navigation-compose:2.8.0-alpha08")
+    api("androidx.navigation:navigation-compose:2.8.0-beta01")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
     api("com.google.android.material:material:1.11.0")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
index 1a10bf0..2cf0d3b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
@@ -29,6 +29,7 @@
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.LinkAnnotation
 import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLinkStyles
 import androidx.compose.ui.text.buildAnnotatedString
 import androidx.compose.ui.text.font.FontStyle
 import androidx.compose.ui.text.font.FontWeight
@@ -98,7 +99,9 @@
 ) {
     val url = LinkAnnotation.Url(
         url = urlSpan.url,
-        style = SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline),
+        styles = TextLinkStyles(
+            style = SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline),
+        ),
     )
     addLink(url, start, end)
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
index 612f9e5..4c1efd7 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
@@ -21,6 +21,7 @@
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.LinkAnnotation
 import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLinkStyles
 import androidx.compose.ui.text.font.FontStyle
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.style.TextDecoration
@@ -47,9 +48,11 @@
                 AnnotatedString.Range(
                     item = LinkAnnotation.Url(
                         url = "https://www.android.com/",
-                        style = SpanStyle(
-                            color = MaterialTheme.colorScheme.primary,
-                            textDecoration = TextDecoration.Underline,
+                        styles = TextLinkStyles(
+                            style = SpanStyle(
+                                color = MaterialTheme.colorScheme.primary,
+                                textDecoration = TextDecoration.Underline,
+                            ),
                         ),
                     ),
                     start = 31,
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index d2e5a13..e03ac3d 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -11,6 +11,7 @@
 alexflo@google.com
 andonian@google.com
 amiko@google.com
+austindelgado@google.com
 aroederer@google.com
 arteiro@google.com
 asc@google.com
@@ -29,11 +30,13 @@
 cinek@google.com
 cocod@google.com
 darrellshi@google.com
+diyab@google.com
 dupin@google.com
 ethibodeau@google.com
 evanlaird@google.com
 florenceyang@google.com
 gallmann@google.com
+graciecheng@google.com
 gwasserman@google.com
 hwwang@google.com
 hyunyoungs@google.com
@@ -42,10 +45,12 @@
 jbolinger@google.com
 jdemeulenaere@google.com
 jeffdq@google.com
+jeffpu@google.com
 jernej@google.com
 jglazier@google.com
 jjaggi@google.com
 jonmiranda@google.com
+joshmccloskey@google.com
 joshtrask@google.com
 juansmartinez@google.com
 juliacr@google.com
@@ -87,6 +92,7 @@
 santie@google.com
 shanh@google.com
 snoeberger@google.com
+spdonghao@google.com
 steell@google.com
 stevenckng@google.com
 stwu@google.com
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c61f996..1cbf67e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1048,15 +1048,6 @@
   bug: "343505271"
 }
 
-flag {
-  name: "glanceable_hub_animate_timer_activity_starts"
-  namespace: "systemui"
-  description: "Properly animates activity starts from live timers on the glanceable hub"
-  bug: "345741071"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
 
 flag {
   name: "new_touchpad_gestures_tutorial"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index b353b5a..18085ab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -25,9 +25,9 @@
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
 import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
 import com.android.systemui.keyguard.ui.composable.section.LockSection
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
@@ -38,7 +38,7 @@
 @Inject
 constructor(
     private val viewModel: CommunalViewModel,
-    private val interactionHandler: WidgetInteractionHandler,
+    private val interactionHandler: SmartspaceInteractionHandler,
     private val dialogFactory: SystemUIDialogFactory,
     private val lockSection: LockSection,
     private val ambientStatusBarSection: AmbientStatusBarSection,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index a1899fd..927890e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -135,7 +135,6 @@
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.compose.ui.graphics.painter.rememberDrawablePainter
 import com.android.internal.R.dimen.system_app_widget_background_radius
-import com.android.systemui.Flags.glanceableHubAnimateTimerActivityStarts
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.shared.model.CommunalScenes
@@ -1209,9 +1208,7 @@
         modifier = modifier,
         factory = { context ->
             SmartspaceAppWidgetHostView(context).apply {
-                if (glanceableHubAnimateTimerActivityStarts()) {
-                    interactionHandler?.let { setInteractionHandler(it) }
-                }
+                interactionHandler?.let { setInteractionHandler(it) }
                 updateAppWidget(model.remoteViews)
             }
         },
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 18e65508..22566e7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -99,7 +99,7 @@
                         if (sceneKey == currentSceneKey) {
                             currentDestinations
                         } else {
-                            composableScene.destinationScenes.value
+                            viewModel.resolveSceneFamilies(composableScene.destinationScenes.value)
                         },
                 ) {
                     with(composableScene) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
new file mode 100644
index 0000000..0cd3fb2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 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.communal.smartspace
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.RemoteViews.RemoteResponse
+import androidx.core.util.component1
+import androidx.core.util.component2
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
+import com.android.systemui.plugins.ActivityStarter
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.isNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.notNull
+import org.mockito.kotlin.refEq
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SmartspaceInteractionHandlerTest : SysuiTestCase() {
+    private val activityStarter = mock<ActivityStarter>()
+
+    private val testIntent =
+        PendingIntent.getActivity(
+            context,
+            /* requestCode = */ 0,
+            Intent("action"),
+            PendingIntent.FLAG_IMMUTABLE
+        )
+    private val testResponse = RemoteResponse.fromPendingIntent(testIntent)
+
+    private val underTest: SmartspaceInteractionHandler by lazy {
+        SmartspaceInteractionHandler(activityStarter)
+    }
+
+    @Test
+    fun launchAnimatorIsUsedForSmartspaceView() {
+        val parent = FrameLayout(context)
+        val view = SmartspaceAppWidgetHostView(context)
+        parent.addView(view)
+        val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
+
+        underTest.onInteraction(view, testIntent, testResponse)
+
+        // Verify that we pass in a non-null animation controller
+        verify(activityStarter)
+            .startPendingIntentWithoutDismissing(
+                /* intent = */ eq(testIntent),
+                /* dismissShade = */ eq(false),
+                /* intentSentUiThreadCallback = */ isNull(),
+                /* animationController = */ notNull(),
+                /* fillInIntent = */ refEq(fillInIntent),
+                /* extraOptions = */ refEq(activityOptions.toBundle()),
+            )
+    }
+
+    @Test
+    fun launchAnimatorIsNotUsedForRegularView() {
+        val parent = FrameLayout(context)
+        val view = View(context)
+        parent.addView(view)
+        val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
+
+        underTest.onInteraction(view, testIntent, testResponse)
+
+        // Verify null is used as the animation controller
+        verify(activityStarter)
+            .startPendingIntentWithoutDismissing(
+                /* intent = */ eq(testIntent),
+                /* dismissShade = */ eq(false),
+                /* intentSentUiThreadCallback = */ isNull(),
+                /* animationController = */ isNull(),
+                /* fillInIntent = */ refEq(fillInIntent),
+                /* extraOptions = */ refEq(activityOptions.toBundle()),
+            )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
index f9d5073..0250c9d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
@@ -19,17 +19,23 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalTransitionViewModelTest : SysuiTestCase() {
@@ -49,13 +55,9 @@
     fun testIsUmoOnCommunalDuringTransitionBetweenLockscreenAndGlanceableHub() =
         testScope.runTest {
             val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
-            assertThat(isUmoOnCommunal).isNull()
+            runCurrent()
 
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.GLANCEABLE_HUB,
-                testScope
-            )
+            enterCommunal(from = KeyguardState.LOCKSCREEN)
             assertThat(isUmoOnCommunal).isTrue()
 
             keyguardTransitionRepository.sendTransitionSteps(
@@ -70,13 +72,9 @@
     fun testIsUmoOnCommunalDuringTransitionBetweenDreamingAndGlanceableHub() =
         testScope.runTest {
             val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
-            assertThat(isUmoOnCommunal).isNull()
+            runCurrent()
 
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.DREAMING,
-                to = KeyguardState.GLANCEABLE_HUB,
-                testScope
-            )
+            enterCommunal(from = KeyguardState.DREAMING)
             assertThat(isUmoOnCommunal).isTrue()
 
             keyguardTransitionRepository.sendTransitionSteps(
@@ -91,13 +89,9 @@
     fun testIsUmoOnCommunalDuringTransitionBetweenOccludedAndGlanceableHub() =
         testScope.runTest {
             val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
-            assertThat(isUmoOnCommunal).isNull()
+            runCurrent()
 
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.OCCLUDED,
-                to = KeyguardState.GLANCEABLE_HUB,
-                testScope
-            )
+            enterCommunal(from = KeyguardState.OCCLUDED)
             assertThat(isUmoOnCommunal).isTrue()
 
             keyguardTransitionRepository.sendTransitionSteps(
@@ -105,7 +99,33 @@
                 to = KeyguardState.OCCLUDED,
                 testScope
             )
-
             assertThat(isUmoOnCommunal).isFalse()
         }
+
+    @Test
+    fun isUmoOnCommunal_noLongerVisible_returnsFalse() =
+        testScope.runTest {
+            val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
+            runCurrent()
+
+            enterCommunal(from = KeyguardState.LOCKSCREEN)
+            assertThat(isUmoOnCommunal).isTrue()
+
+            // Communal is no longer visible.
+            kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Blank)
+            runCurrent()
+
+            // isUmoOnCommunal returns false, even without any keyguard transition.
+            assertThat(isUmoOnCommunal).isFalse()
+        }
+
+    private suspend fun TestScope.enterCommunal(from: KeyguardState) {
+        keyguardTransitionRepository.sendTransitionSteps(
+            from = from,
+            to = KeyguardState.GLANCEABLE_HUB,
+            testScope
+        )
+        kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal)
+        runCurrent()
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
index df7b291..7b7d03b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
@@ -63,34 +63,15 @@
 
         underTest.onInteraction(view, testIntent, testResponse)
 
+        // Verify that we pass in a non-null animation controller
         verify(activityStarter)
             .startPendingIntentMaybeDismissingKeyguard(
-                eq(testIntent),
-                eq(false),
-                isNull(),
-                notNull(),
-                refEq(fillInIntent),
-                refEq(activityOptions.toBundle()),
-            )
-    }
-
-    @Test
-    fun launchAnimatorIsUsedForSmartspaceView() {
-        val parent = FrameLayout(context)
-        val view = SmartspaceAppWidgetHostView(context)
-        parent.addView(view)
-        val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
-
-        underTest.onInteraction(view, testIntent, testResponse)
-
-        verify(activityStarter)
-            .startPendingIntentMaybeDismissingKeyguard(
-                eq(testIntent),
-                eq(false),
-                isNull(),
-                notNull(),
-                refEq(fillInIntent),
-                refEq(activityOptions.toBundle()),
+                /* intent = */ eq(testIntent),
+                /* dismissShade = */ eq(false),
+                /* intentSentUiThreadCallback = */ isNull(),
+                /* animationController = */ notNull(),
+                /* fillInIntent = */ refEq(fillInIntent),
+                /* extraOptions = */ refEq(activityOptions.toBundle()),
             )
     }
 
@@ -103,14 +84,15 @@
 
         underTest.onInteraction(view, testIntent, testResponse)
 
+        // Verify null is used as the animation controller
         verify(activityStarter)
             .startPendingIntentMaybeDismissingKeyguard(
-                eq(testIntent),
-                eq(false),
-                isNull(),
-                isNull(),
-                refEq(fillInIntent),
-                refEq(activityOptions.toBundle()),
+                /* intent = */ eq(testIntent),
+                /* dismissShade = */ eq(false),
+                /* intentSentUiThreadCallback = */ isNull(),
+                /* animationController = */ isNull(),
+                /* fillInIntent = */ refEq(fillInIntent),
+                /* extraOptions = */ refEq(activityOptions.toBundle()),
             )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index f375ec7..5dac37a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -333,27 +333,16 @@
         }
 
     @Test
-    fun isDreamingFromKeyguardUpdateMonitor() =
-        TestScope(mainDispatcher).runTest {
-            whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
-            var latest: Boolean? = null
-            val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
+    fun isDreaming() =
+        testScope.runTest {
+            val isDreaming by collectLastValue(underTest.isDreaming)
+            assertThat(isDreaming).isFalse()
 
-            runCurrent()
-            assertThat(latest).isFalse()
+            underTest.setDreaming(true)
+            assertThat(isDreaming).isTrue()
 
-            val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
-            verify(keyguardUpdateMonitor).registerCallback(captor.capture())
-
-            captor.value.onDreamingStateChanged(true)
-            runCurrent()
-            assertThat(latest).isTrue()
-
-            captor.value.onDreamingStateChanged(false)
-            runCurrent()
-            assertThat(latest).isFalse()
-
-            job.cancel()
+            underTest.setDreaming(false)
+            assertThat(isDreaming).isFalse()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt
index 79671b8..bf3231e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt
@@ -53,7 +53,7 @@
     @Test
     fun lockscreenAlpha() =
         testScope.runTest {
-            val viewState = ViewStateAccessor(alpha = { 0.6f })
+            val viewState = ViewStateAccessor()
             val alpha by collectValues(underTest.lockscreenAlpha(viewState))
 
             keyguardTransitionRepository.sendTransitionSteps(
@@ -62,11 +62,9 @@
                 testScope
             )
 
-            assertThat(alpha[0]).isEqualTo(0.6f)
-            // Fades out just prior to halfway
+            // Remain at zero throughout
+            assertThat(alpha[0]).isEqualTo(0f)
             assertThat(alpha[1]).isEqualTo(0f)
-            // Must finish at 0
-            assertThat(alpha[2]).isEqualTo(0f)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelTest.kt
index 59a6ce7..80a9532 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelTest.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -32,6 +33,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
+import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
@@ -104,6 +106,24 @@
             values.forEach { assertThat(it).isNull() }
         }
 
+    @Test
+    fun notificationAlpha_fadesOut() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.notificationAlpha)
+
+            keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            assertThat(alpha).isEqualTo(1f)
+
+            keyguardTransitionRepository.sendTransitionStep(step(0.25f))
+            assertThat(alpha).isIn(Range.open(.25f, .75f))
+
+            keyguardTransitionRepository.sendTransitionStep(step(1f))
+            assertThat(alpha).isEqualTo(0f)
+
+            keyguardTransitionRepository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+            assertThat(alpha).isEqualTo(1f)
+        }
+
     private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
         return TransitionStep(
             from = KeyguardState.GONE,
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index de65931..7cf56aa 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -58,6 +58,19 @@
             @Nullable ActivityTransitionAnimator.Controller animationController);
 
     /**
+     * Similar to {@link #startPendingIntentMaybeDismissingKeyguard(PendingIntent, Runnable,
+     * ActivityTransitionAnimator.Controller)} but will always not dismiss the keyguard when
+     * launching activities. This should be avoided and other alternatives should be used.
+     */
+    void startPendingIntentWithoutDismissing(
+            PendingIntent intent,
+            boolean dismissShade,
+            Runnable intentSentUiThreadCallback,
+            @Nullable ActivityTransitionAnimator.Controller animationController,
+            @Nullable Intent fillInIntent,
+            @Nullable Bundle extraOptions);
+
+    /**
      * Similar to {@link #startPendingIntentDismissingKeyguard}, except that it supports launching
      * activities on top of the keyguard. If the activity supports {@code showOverLockscreen}, it
      * will show over keyguard without first dimissing it. If it doesn't support it, calling this
diff --git a/packages/SystemUI/res/drawable/ic_swipe_down.xml b/packages/SystemUI/res/drawable/ic_swipe_down.xml
new file mode 100644
index 0000000..15712d6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_swipe_down.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M180,600L40,460L82,418L152,488Q146,461 143,434Q140,407 140,380Q140,298 167,221Q194,144 245,80L288,123Q245,179 222.5,244.5Q200,310 200,380Q200,406 203,431.5Q206,457 213,482L278,418L320,460L180,600ZM658,833Q635,841 611.5,840.5Q588,840 566,829L304,707L322,667Q332,647 350,634.5Q368,622 390,620L458,615L346,308Q340,292 347,277.5Q354,263 370,257Q386,251 400.5,258Q415,265 421,281L569,688L469,695L600,756Q607,759 615,759.5Q623,760 630,758L787,701Q818,690 832,659.5Q846,629 835,598L780,448Q774,432 781,417.5Q788,403 804,397Q820,391 834.5,398Q849,405 855,421L910,571Q933,634 905.5,693.5Q878,753 815,776L658,833ZM568,568L514,417Q508,401 515,386.5Q522,372 538,366Q554,360 568.5,367Q583,374 589,390L644,540L568,568ZM681,527L640,414Q634,398 641,383.5Q648,369 664,363Q680,357 694.5,364Q709,371 715,387L756,499L681,527ZM689,605L689,605L689,605Q689,605 689,605Q689,605 689,605L689,605Q689,605 689,605Q689,605 689,605L689,605L689,605Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/immersive_cling_bg_circ.xml b/packages/SystemUI/res/drawable/immersive_cling_bg.xml
similarity index 67%
copy from core/res/res/drawable/immersive_cling_bg_circ.xml
copy to packages/SystemUI/res/drawable/immersive_cling_bg.xml
index 4731bbd..de29c32 100644
--- a/core/res/res/drawable/immersive_cling_bg_circ.xml
+++ b/packages/SystemUI/res/drawable/immersive_cling_bg.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2015 The Android Open Source Project
+  ~ Copyright (C) 2024 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.
@@ -15,12 +15,10 @@
   ~ limitations under the License
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval" >
-
-    <solid android:color="@color/white" />
-
-    <size
-        android:height="56dp"
-        android:width="56dp" />
-
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <corners
+        android:bottomLeftRadius="28dp"
+        android:bottomRightRadius="28dp"/>
+    <solid android:color="?androidprv:attr/materialColorSurfaceContainer" />
 </shape>
diff --git a/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml b/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml
deleted file mode 100644
index 32e88ab..0000000
--- a/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval" >
-
-    <solid android:color="?android:attr/colorBackground" />
-
-    <size
-        android:height="56dp"
-        android:width="56dp" />
-
-</shape>
diff --git a/packages/SystemUI/res/drawable/immersive_cling_btn_bg.xml b/packages/SystemUI/res/drawable/immersive_cling_btn_bg.xml
new file mode 100644
index 0000000..df49e38
--- /dev/null
+++ b/packages/SystemUI/res/drawable/immersive_cling_btn_bg.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+    <ripple android:color="?android:attr/colorControlHighlight">
+        <item android:id="@android:id/mask">
+            <shape android:shape="rectangle">
+                <solid android:color="@android:color/white" />
+                <corners android:radius="28dp" />
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="rectangle">
+                <corners android:radius="28dp" />
+                <solid android:color="?android:attr/colorAccent" />
+            </shape>
+        </item>
+    </ripple>
+</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml b/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml
deleted file mode 100644
index 12c3e23..0000000
--- a/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval" >
-
-    <solid android:color="?android:attr/colorBackground" />
-
-    <size
-        android:height="76dp"
-        android:width="76dp" />
-
-</shape>
diff --git a/packages/SystemUI/res/layout/immersive_mode_cling.xml b/packages/SystemUI/res/layout/immersive_mode_cling.xml
index e6529b9..20b7cd3 100644
--- a/packages/SystemUI/res/layout/immersive_mode_cling.xml
+++ b/packages/SystemUI/res/layout/immersive_mode_cling.xml
@@ -14,78 +14,67 @@
      limitations under the License.
 -->
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:theme="@android:style/Theme.DeviceDefault.DayNight"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/immersive_cling_bg"
+    android:gravity="center_vertical"
+    android:padding="24dp">
+
+    <!-- The top margin of this icon can be adjusted to push the content down to prevent overlapping
+         with the display cutout. -->
+    <ImageView
+        android:id="@+id/immersive_cling_icon"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_centerHorizontal="true"
+        android:scaleType="fitXY"
+        android:src="@drawable/ic_swipe_down"
+        android:tint="?android:attr/colorAccent"
+        android:tintMode="src_in" />
+
+    <TextView
+        android:id="@+id/immersive_cling_title"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:gravity="center_vertical"
-        android:paddingBottom="24dp">
-
-    <FrameLayout
-            android:id="@+id/immersive_cling_chevron"
-            android:layout_width="76dp"
-            android:layout_height="76dp"
-            android:layout_marginTop="-24dp"
-            android:layout_centerHorizontal="true">
-
-        <ImageView
-                android:id="@+id/immersive_cling_back_bg_light"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:scaleType="center"
-                android:src="@drawable/immersive_cling_light_bg_circ" />
-
-        <ImageView
-                android:id="@+id/immersive_cling_back_bg"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:scaleType="center"
-                android:src="@drawable/immersive_cling_bg_circ" />
-
-        <ImageView
-                android:id="@+id/immersive_cling_ic_expand_more"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:paddingTop="8dp"
-                android:scaleType="center"
-                android:src="@drawable/ic_expand_more_48dp"/>
-    </FrameLayout>
+        android:layout_below="@id/immersive_cling_icon"
+        android:layout_marginTop="20dp"
+        android:gravity="center_horizontal"
+        android:text="@string/immersive_cling_title"
+        android:textColor="?androidprv:attr/materialColorOnSurface"
+        android:textSize="24sp"
+        android:fontFamily="google-sans" />
 
     <TextView
-            android:id="@+id/immersive_cling_title"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_below="@id/immersive_cling_chevron"
-            android:paddingEnd="48dp"
-            android:paddingStart="48dp"
-            android:paddingTop="40dp"
-            android:text="@string/immersive_cling_title"
-            android:textColor="?android:attr/textColorPrimaryInverse"
-            android:textSize="24sp" />
-
-    <TextView
-            android:id="@+id/immersive_cling_description"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_below="@id/immersive_cling_title"
-            android:paddingEnd="48dp"
-            android:paddingStart="48dp"
-            android:paddingTop="12.6dp"
-            android:text="@string/immersive_cling_description"
-            android:textColor="?android:attr/textColorPrimaryInverse"
-            android:textSize="16sp" />
+        android:id="@+id/immersive_cling_description"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/immersive_cling_title"
+        android:paddingTop="14dp"
+        android:gravity="center_horizontal"
+        android:text="@string/immersive_cling_description"
+        android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+        android:textSize="14sp"
+        android:fontFamily="google-sans" />
 
     <Button
-            android:id="@+id/ok"
-            style="@android:style/Widget.Material.Button.Borderless"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentEnd="true"
-            android:layout_below="@+id/immersive_cling_description"
-            android:layout_marginEnd="40dp"
-            android:layout_marginTop="18dp"
-            android:paddingEnd="8dp"
-            android:paddingStart="8dp"
-            android:text="@string/immersive_cling_positive"
-            android:textColor="?android:attr/textColorPrimaryInverse"
-            android:textSize="14sp" />
-
+        android:id="@+id/ok"
+        style="@android:style/Widget.Material.Button.Borderless.Colored"
+        android:background="@drawable/immersive_cling_btn_bg"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_below="@+id/immersive_cling_description"
+        android:layout_marginTop="24dp"
+        android:paddingStart="18dp"
+        android:paddingEnd="18dp"
+        android:minWidth="48dp"
+        android:minHeight="48dp"
+        android:text="@string/immersive_cling_positive"
+        android:textColor="?androidprv:attr/materialColorOnPrimary"
+        android:textAllCaps="false"
+        android:textSize="14sp"
+        android:textFontWeight="500"
+        android:fontFamily="google-sans" />
 </RelativeLayout>
diff --git a/packages/SystemUI/res/raw/keep.xml b/packages/SystemUI/res/raw/keep_homecontrols_sq.xml
similarity index 100%
rename from packages/SystemUI/res/raw/keep.xml
rename to packages/SystemUI/res/raw/keep_homecontrols_sq.xml
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index aea79e8..235015b 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -34,7 +34,7 @@
     <dimen name="volume_row_slider_height">128dp</dimen>
 
     <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
-    <dimen name="immersive_mode_cling_width">380dp</dimen>
+    <dimen name="immersive_mode_cling_width">500dp</dimen>
 
     <dimen name="controls_activity_view_top_offset">25dp</dimen>
 
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 29e0dbe..27af334 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -56,7 +56,7 @@
     <dimen name="navigation_key_padding">25dp</dimen>
 
     <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
-    <dimen name="immersive_mode_cling_width">380dp</dimen>
+    <dimen name="immersive_mode_cling_width">600dp</dimen>
 
     <!-- Keyboard shortcuts helper -->
     <dimen name="ksh_layout_width">488dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6a54c2f..79be2b1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -330,6 +330,8 @@
     <string name="share_to_app_stop_dialog_title">Stop sharing screen?</string>
     <!-- Text telling a user that they will stop sharing their screen if they click the "Stop sharing" button [CHAR LIMIT=100] -->
     <string name="share_to_app_stop_dialog_message">You will stop sharing your screen</string>
+    <!-- Text telling a user that they will stop sharing the contents of the specified [app_name] if they click the "Stop sharing" button. Note that the app name will appear in bold. [CHAR LIMIT=100] -->
+    <string name="share_to_app_stop_dialog_message_specific_app">You will stop sharing &lt;b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g>&lt;/b></string>
     <!-- Button to stop screen sharing [CHAR LIMIT=35] -->
     <string name="share_to_app_stop_dialog_button">Stop sharing</string>
 
@@ -337,6 +339,8 @@
     <string name="cast_to_other_device_stop_dialog_title">Stop casting screen?</string>
     <!-- Text telling a user that they will stop casting their screen to a different device if they click the "Stop casting" button [CHAR LIMIT=100] -->
     <string name="cast_to_other_device_stop_dialog_message">You will stop casting your screen</string>
+    <!-- Text telling a user that they will stop casting the contents of the specified [app_name] to a different device if they click the "Stop casting" button. Note that the app name will appear in bold.  [CHAR LIMIT=100] -->
+    <string name="cast_to_other_device_stop_dialog_message_specific_app">You will stop casting &lt;b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g>&lt;/b></string>
     <!-- Button to stop screen casting to a different device [CHAR LIMIT=35] -->
     <string name="cast_to_other_device_stop_dialog_button">Stop casting</string>
 
@@ -367,7 +371,7 @@
     <!-- Cling help message title when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
     <string name="immersive_cling_title">Viewing full screen</string>
     <!-- Cling help message description when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
-    <string name="immersive_cling_description">To exit, swipe down from the top.</string>
+    <string name="immersive_cling_description">To exit, swipe down from the top of your screen</string>
     <!-- Cling help message confirmation button when hiding the navigation bar entering immersive mode [CHAR LIMIT=30] -->
     <string name="immersive_cling_positive">Got it</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 4c9af66..e055e7c 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -1250,6 +1250,11 @@
         if (mOverlays == null) {
             return;
         }
+        if (mPendingConfigChange) {
+            // Let RestartingPreDrawListener's onPreDraw call updateConfiguration
+            // -> updateOverlayProviderViews to redraw with display change synchronously.
+            return;
+        }
         ++mProviderRefreshToken;
         for (final OverlayWindow overlay: mOverlays) {
             if (overlay == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
new file mode 100644
index 0000000..a88b777
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 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.communal.smartspace
+
+import android.app.ActivityOptions
+import android.app.PendingIntent
+import android.content.Intent
+import android.view.View
+import android.widget.RemoteViews
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.communal.util.InteractionHandlerDelegate
+import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
+import com.android.systemui.plugins.ActivityStarter
+import javax.inject.Inject
+
+/**
+ * Handles interactions on smartspace elements on the hub.
+ */
+class SmartspaceInteractionHandler @Inject constructor(
+    private val activityStarter: ActivityStarter,
+) : RemoteViews.InteractionHandler {
+    private val delegate = InteractionHandlerDelegate(
+        findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView },
+        intentStarter = this::startIntent,
+    )
+
+    override fun onInteraction(
+        view: View,
+        pendingIntent: PendingIntent,
+        response: RemoteViews.RemoteResponse
+    ): Boolean = delegate.onInteraction(view, pendingIntent, response)
+
+    private fun startIntent(
+        pendingIntent: PendingIntent,
+        fillInIntent: Intent,
+        extraOptions: ActivityOptions,
+        animationController: ActivityTransitionAnimator.Controller?
+    ): Boolean {
+        activityStarter.startPendingIntentWithoutDismissing(
+            pendingIntent,
+            /* dismissShade = */ false,
+            /* intentSentUiThreadCallback = */ null,
+            animationController,
+            fillInIntent,
+            extraOptions.toBundle()
+        )
+        return true
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index 7a20ebc..fce18a26 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.Color
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -29,11 +30,11 @@
 import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
@@ -50,16 +51,18 @@
     dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
     glanceableHubToDreamTransitionViewModel: GlanceableHubToDreamingTransitionViewModel,
     communalInteractor: CommunalInteractor,
-    keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    communalSceneInteractor: CommunalSceneInteractor,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor
 ) {
     // Show UMO on glanceable hub immediately on transition into glanceable hub
     private val showUmoFromOccludedToGlanceableHub: Flow<Boolean> =
         keyguardTransitionInteractor
-            .transition(Edge.create(from = KeyguardState.OCCLUDED))
+            .transition(
+                Edge.create(from = KeyguardState.OCCLUDED, to = KeyguardState.GLANCEABLE_HUB)
+            )
             .filter {
-                it.to == KeyguardState.GLANCEABLE_HUB &&
-                    (it.transitionState == TransitionState.STARTED ||
-                        it.transitionState == TransitionState.CANCELED)
+                (it.transitionState == TransitionState.STARTED ||
+                    it.transitionState == TransitionState.CANCELED)
             }
             .map { it.transitionState == TransitionState.STARTED }
 
@@ -82,7 +85,13 @@
      * of UMO should be updated.
      */
     val isUmoOnCommunal: Flow<Boolean> =
-        merge(
+        allOf(
+            // Only show UMO on the hub if the hub is at least partially visible. This prevents
+            // the UMO from being missing on the lock screen when going from the hub to lock
+            // screen in some way other than through a direct transition, such as unlocking from
+            // the hub, then pressing power twice to go back to the lock screen.
+            communalSceneInteractor.isCommunalVisible,
+            merge(
                 lockscreenToGlanceableHubTransitionViewModel.showUmo,
                 glanceableHubToLockscreenTransitionViewModel.showUmo,
                 dreamToGlanceableHubTransitionViewModel.showUmo,
@@ -90,7 +99,7 @@
                 showUmoFromOccludedToGlanceableHub,
                 showUmoFromGlanceableHubToOccluded,
             )
-            .distinctUntilChanged()
+        )
 
     /** Whether to show communal when exiting the occluded state. */
     val showCommunalFromOccluded: Flow<Boolean> = communalInteractor.showCommunalFromOccluded
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
new file mode 100644
index 0000000..40b182d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 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.communal.util
+
+import android.app.ActivityOptions
+import android.app.PendingIntent
+import android.content.Intent
+import android.view.View
+import android.widget.RemoteViews
+import androidx.core.util.component1
+import androidx.core.util.component2
+import com.android.systemui.animation.ActivityTransitionAnimator
+
+
+/** A delegate that can be used to launch activities from [RemoteViews] */
+class InteractionHandlerDelegate(
+    private val findViewToAnimate: (View) -> Boolean,
+    private val intentStarter: IntentStarter,
+) : RemoteViews.InteractionHandler {
+
+    /**
+     * Responsible for starting the pending intent for launching activities.
+     */
+    fun interface IntentStarter {
+        fun startPendingIntent(
+            intent: PendingIntent,
+            fillInIntent: Intent,
+            activityOptions: ActivityOptions,
+            controller: ActivityTransitionAnimator.Controller?,
+        ): Boolean
+    }
+
+    override fun onInteraction(
+        view: View,
+        pendingIntent: PendingIntent,
+        response: RemoteViews.RemoteResponse
+    ): Boolean {
+        val launchOptions = response.getLaunchOptions(view)
+        return when {
+            pendingIntent.isActivity -> {
+                // Forward the fill-in intent and activity options retrieved from the response
+                // to populate the pending intent, so that list items can launch respective
+                // activities.
+                val hostView = getNearestParent(view)
+                val animationController =
+                    hostView?.let(ActivityTransitionAnimator.Controller::fromView)
+                val (fillInIntent, activityOptions) = launchOptions
+                intentStarter.startPendingIntent(
+                    pendingIntent,
+                    fillInIntent,
+                    activityOptions,
+                    animationController
+                )
+            }
+
+            else -> RemoteViews.startPendingIntent(view, pendingIntent, launchOptions)
+        }
+    }
+
+    private fun getNearestParent(child: View): View? {
+        var view: Any? = child
+        while (view is View) {
+            if (findViewToAnimate(view)) return view
+            view = view.parent
+        }
+        return null
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
index e88a8b5..cbc6c97 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -18,15 +18,11 @@
 
 import android.app.ActivityOptions
 import android.app.PendingIntent
-import android.appwidget.AppWidgetHostView
 import android.content.Intent
-import android.util.Pair
 import android.view.View
 import android.widget.RemoteViews
-import androidx.core.util.component1
-import androidx.core.util.component2
 import com.android.systemui.animation.ActivityTransitionAnimator
-import com.android.systemui.common.ui.view.getNearestParent
+import com.android.systemui.communal.util.InteractionHandlerDelegate
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.ActivityStarter
 import javax.inject.Inject
@@ -37,38 +33,32 @@
 constructor(
     private val activityStarter: ActivityStarter,
 ) : RemoteViews.InteractionHandler {
+
+    private val delegate = InteractionHandlerDelegate(
+        findViewToAnimate = { view -> view is CommunalAppWidgetHostView },
+        intentStarter = this::startIntent,
+    )
+
     override fun onInteraction(
         view: View,
         pendingIntent: PendingIntent,
         response: RemoteViews.RemoteResponse
-    ): Boolean {
-        val launchOptions = response.getLaunchOptions(view)
-        return when {
-            pendingIntent.isActivity ->
-                // Forward the fill-in intent and activity options retrieved from the response
-                // to populate the pending intent, so that list items can launch respective
-                // activities.
-                startActivity(view, pendingIntent, launchOptions)
-            else -> RemoteViews.startPendingIntent(view, pendingIntent, launchOptions)
-        }
-    }
+    ): Boolean = delegate.onInteraction(view, pendingIntent, response)
 
-    private fun startActivity(
-        view: View,
+
+    private fun startIntent(
         pendingIntent: PendingIntent,
-        launchOptions: Pair<Intent, ActivityOptions>,
+        fillInIntent: Intent,
+        extraOptions: ActivityOptions,
+        controller: ActivityTransitionAnimator.Controller?
     ): Boolean {
-        val hostView = view.getNearestParent<AppWidgetHostView>()
-        val animationController = hostView?.let(ActivityTransitionAnimator.Controller::fromView)
-        val (fillInIntent, activityOptions) = launchOptions
-
         activityStarter.startPendingIntentMaybeDismissingKeyguard(
             pendingIntent,
             /* dismissShade = */ false,
             /* intentSentUiThreadCallback = */ null,
-            animationController,
+            controller,
             fillInIntent,
-            activityOptions.toBundle(),
+            extraOptions.toBundle(),
         )
         return true
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index f2a544e..209bc7a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -80,6 +80,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
 import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
@@ -118,6 +119,7 @@
     private final ShellTransitions mShellTransitions;
     private final DisplayTracker mDisplayTracker;
     private final PowerInteractor mPowerInteractor;
+    private final KeyguardInteractor mKeyguardInteractor;
     private final Lazy<SceneInteractor> mSceneInteractorLazy;
     private final Executor mMainExecutor;
 
@@ -338,6 +340,7 @@
             WindowManagerOcclusionManager windowManagerOcclusionManager,
             Lazy<SceneInteractor> sceneInteractorLazy,
             @Main Executor mainExecutor,
+            KeyguardInteractor keyguardInteractor,
             KeyguardEnabledInteractor keyguardEnabledInteractor) {
         super();
         mKeyguardViewMediator = keyguardViewMediator;
@@ -347,6 +350,7 @@
         mDisplayTracker = displayTracker;
         mFlags = featureFlags;
         mPowerInteractor = powerInteractor;
+        mKeyguardInteractor = keyguardInteractor;
         mSceneInteractorLazy = sceneInteractorLazy;
         mMainExecutor = mainExecutor;
 
@@ -474,6 +478,7 @@
         public void onDreamingStarted() {
             trace("onDreamingStarted");
             checkPermission();
+            mKeyguardInteractor.setDreaming(true);
             mKeyguardViewMediator.onDreamingStarted();
         }
 
@@ -481,6 +486,7 @@
         public void onDreamingStopped() {
             trace("onDreamingStopped");
             checkPermission();
+            mKeyguardInteractor.setDreaming(false);
             mKeyguardViewMediator.onDreamingStopped();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index cf83582..00566c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -712,7 +712,6 @@
         // As soon as the shade starts animating out of the way, start the canned unlock animation,
         // which will finish keyguard exit when it completes. The in-window animations in the
         // Launcher window will end on their own.
-        if (fastUnlockTransition()) hideKeyguardViewAfterRemoteAnimation()
         handler.postDelayed({
             if (keyguardViewMediator.get().isShowingAndNotOccluded &&
                 !keyguardStateController.isKeyguardGoingAway) {
@@ -723,7 +722,7 @@
 
             if ((wallpaperTargets?.isNotEmpty() == true)) {
                 fadeInWallpaper()
-                if (!fastUnlockTransition()) hideKeyguardViewAfterRemoteAnimation()
+                hideKeyguardViewAfterRemoteAnimation()
             } else {
                 keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
                     false /* cancelled */)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index f3a1843..2d60fcc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -176,6 +176,8 @@
 import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -185,7 +187,6 @@
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
-import dagger.Lazy;
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -3014,7 +3015,7 @@
                         && mPM.isInteractive(), WakeAndUnlockUpdateReason.HIDE);
             }
 
-            if ((mShowing && !mOccluded) || mUnlockingAndWakingFromDream) {
+            if (mBootCompleted && ((mShowing && !mOccluded) || mUnlockingAndWakingFromDream)) {
                 if (mUnlockingAndWakingFromDream) {
                     Log.d(TAG, "hiding keyguard before waking from dream");
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index f15896b..d508b2b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -148,7 +148,7 @@
      * Dozing/AOD is a specific type of dream, but it is also possible for other non-systemui dreams
      * to be active, such as screensavers.
      */
-    val isDreaming: Flow<Boolean>
+    val isDreaming: MutableStateFlow<Boolean>
 
     /** Observable for whether the device is dreaming with an overlay, see [DreamOverlayService] */
     val isDreamingWithOverlay: Flow<Boolean>
@@ -250,6 +250,9 @@
     /** Sets the current amount of alpha that should be used for rendering the keyguard. */
     fun setKeyguardAlpha(alpha: Float)
 
+    /** Whether the device is actively dreaming */
+    fun setDreaming(isDreaming: Boolean)
+
     /**
      * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
      */
@@ -509,25 +512,7 @@
             }
             .distinctUntilChanged()
 
-    override val isDreaming: Flow<Boolean> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : KeyguardUpdateMonitorCallback() {
-                        override fun onDreamingStateChanged(isDreaming: Boolean) {
-                            trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
-                        }
-                    }
-                keyguardUpdateMonitor.registerCallback(callback)
-                trySendWithFailureLogging(
-                    keyguardUpdateMonitor.isDreaming,
-                    TAG,
-                    "initial isDreaming",
-                )
-
-                awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
-            }
-            .flowOn(mainDispatcher)
-            .distinctUntilChanged()
+    override val isDreaming: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
     override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
         val callback =
@@ -674,6 +659,10 @@
         _keyguardAlpha.value = alpha
     }
 
+    override fun setDreaming(isDreaming: Boolean) {
+        this.isDreaming.value = isDreaming
+    }
+
     override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported
 
     override fun setQuickSettingsVisible(isVisible: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index f5e98f1..f3692bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
@@ -65,6 +66,7 @@
 
     override fun start() {
         listenForDozingToAny()
+        listenForDozingToGoneViaBiometrics()
         listenForWakeFromDozing()
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
@@ -77,6 +79,35 @@
             isKeyguardDismissible && !isKeyguardShowing
         }
 
+    private fun listenForDozingToGoneViaBiometrics() {
+        if (KeyguardWmStateRefactor.isEnabled) {
+            return
+        }
+
+        // This is separate from `listenForDozingToAny` because any delay on wake and unlock will
+        // cause a noticeable issue with animations
+        scope.launch {
+            powerInteractor.isAwake
+                .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
+                .sample(
+                    keyguardInteractor.biometricUnlockState,
+                    ::Pair,
+                )
+                .collect {
+                    (
+                        _,
+                        biometricUnlockState,
+                    ) ->
+                    if (isWakeAndUnlock(biometricUnlockState.mode)) {
+                        startTransitionTo(
+                            KeyguardState.GONE,
+                            ownerReason = "biometric wake and unlock",
+                        )
+                    }
+                }
+        }
+    }
+
     private fun listenForDozingToAny() {
         if (KeyguardWmStateRefactor.isEnabled) {
             return
@@ -87,7 +118,6 @@
                 .debounce(50L)
                 .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
                 .sample(
-                    keyguardInteractor.biometricUnlockState,
                     keyguardInteractor.isKeyguardOccluded,
                     communalInteractor.isIdleOnCommunal,
                     canTransitionToGoneOnWake,
@@ -96,7 +126,6 @@
                 .collect {
                     (
                         _,
-                        biometricUnlockState,
                         occluded,
                         isIdleOnCommunal,
                         canTransitionToGoneOnWake,
@@ -104,8 +133,6 @@
                     startTransitionTo(
                         if (!deviceEntryRepository.isLockscreenEnabled()) {
                             KeyguardState.GONE
-                        } else if (isWakeAndUnlock(biometricUnlockState.mode)) {
-                            KeyguardState.GONE
                         } else if (canTransitionToGoneOnWake) {
                             KeyguardState.GONE
                         } else if (primaryBouncerShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index a595eeb..ef96be0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -482,6 +482,10 @@
         repository.topClippingBounds.value = top
     }
 
+    fun setDreaming(isDreaming: Boolean) {
+        repository.setDreaming(isDreaming)
+    }
+
     /** Temporary shim, until [KeyguardWmStateRefactor] is enabled */
     fun showKeyguard() {
         fromGoneTransitionInteractor.get().showKeyguard()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
index 77ebfce..480f948 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import android.util.MathUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_GONE_DURATION
 import com.android.systemui.keyguard.shared.model.Edge
@@ -50,12 +49,10 @@
             )
 
     fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
-        var startAlpha = 1f
         return transitionAnimation.sharedFlow(
             duration = 200.milliseconds,
-            onStart = { startAlpha = viewState.alpha() },
-            onStep = { MathUtils.lerp(startAlpha, 0f, it) },
-            onFinish = { 0f },
+            onStart = { 0f },
+            onStep = { 0f },
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt
index 70c0032..c62e4f4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt
@@ -60,6 +60,14 @@
             onFinish = { 1f },
         )
 
+    val notificationAlpha: Flow<Float> =
+        transitionAnimation.sharedFlow(
+            duration = 500.milliseconds,
+            onStep = { 1f - it },
+            // Needs to be 1f in order for HUNs to appear on AOD
+            onFinish = { 1f },
+        )
+
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 987b370..53794d2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -106,7 +106,6 @@
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
@@ -697,7 +696,6 @@
                 .onStart { emit(Unit) }
                 .map { getMediaLockScreenSetting() }
                 .distinctUntilChanged()
-                .flowOn(backgroundDispatcher)
                 .collectLatest {
                     allowMediaPlayerOnLockScreen = it
                     updateHostVisibility()
@@ -886,7 +884,8 @@
                     val previousVisibleIndex =
                         MediaPlayerData.playerKeys().indexOfFirst { key -> it == key }
                     mediaCarouselScrollHandler.scrollToPlayer(previousVisibleIndex, mediaIndex)
-                } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
+                }
+                    ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
             }
         } else if (isRtl && mediaContent.childCount > 0) {
             // In RTL, Scroll to the first player as it is the rightmost player in media carousel.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index c98a49b..08175c3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -330,9 +330,16 @@
      * otherwise returns a singleton [Flow] containing [sceneKey].
      */
     fun resolveSceneFamily(sceneKey: SceneKey): Flow<SceneKey> = flow {
-        emitAll(sceneFamilyResolvers.get()[sceneKey]?.resolvedScene ?: flowOf(sceneKey))
+        emitAll(resolveSceneFamilyOrNull(sceneKey) ?: flowOf(sceneKey))
     }
 
+    /**
+     * Returns the [concrete scene][Scenes] for [sceneKey] if it is a [scene family][SceneFamilies],
+     * otherwise returns `null`.
+     */
+    fun resolveSceneFamilyOrNull(sceneKey: SceneKey): StateFlow<SceneKey>? =
+        sceneFamilyResolvers.get()[sceneKey]?.resolvedScene
+
     private fun isVisibleInternal(
         raw: Boolean = repository.isVisible.value,
         isRemoteUserInteractionOngoing: Boolean = repository.isRemoteUserInteractionOngoing.value,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index ab24e0b..d380251 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -147,6 +147,20 @@
         } ?: true
     }
 
+    /**
+     * Immediately resolves any scene families present in [actionResultMap] to their current
+     * resolution target.
+     */
+    fun resolveSceneFamilies(
+        actionResultMap: Map<UserAction, UserActionResult>,
+    ): Map<UserAction, UserActionResult> {
+        return actionResultMap.mapValues { (_, actionResult) ->
+            sceneInteractor.resolveSceneFamilyOrNull(actionResult.toScene)?.value?.let {
+                actionResult.copy(toScene = it)
+            } ?: actionResult
+        }
+    }
+
     private fun replaceSceneFamilies(
         destinationScenes: Map<UserAction, UserActionResult>,
     ): Flow<Map<UserAction, UserActionResult>> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
index 2dfc920..abf258c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -24,6 +24,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.ViewRootImpl.CLIENT_IMMERSIVE_CONFIRMATION;
 import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
 
@@ -36,12 +37,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.ColorStateList;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.os.Binder;
 import android.os.Bundle;
@@ -71,7 +71,7 @@
 import android.view.animation.Interpolator;
 import android.widget.Button;
 import android.widget.FrameLayout;
-import android.widget.ImageView;
+import android.widget.RelativeLayout;
 
 import com.android.systemui.CoreStartable;
 import com.android.systemui.res.R;
@@ -263,6 +263,7 @@
                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                 PixelFormat.TRANSLUCENT);
         lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars());
+        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         // Trusted overlay so touches outside the touchable area are allowed to pass through
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
                 | WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
@@ -275,13 +276,20 @@
 
     private FrameLayout.LayoutParams getBubbleLayoutParams() {
         return new FrameLayout.LayoutParams(
-                mSysUiContext.getResources().getDimensionPixelSize(
-                        R.dimen.immersive_mode_cling_width),
+                getClingWindowWidth(),
                 ViewGroup.LayoutParams.WRAP_CONTENT,
                 Gravity.CENTER_HORIZONTAL | Gravity.TOP);
     }
 
     /**
+     * Returns the width of the cling window.
+     */
+    private int getClingWindowWidth() {
+        return mSysUiContext.getResources().getDimensionPixelSize(
+                R.dimen.immersive_mode_cling_width);
+    }
+
+    /**
      * @return the window token that's used by all ImmersiveModeConfirmation windows.
      */
     IBinder getWindowToken() {
@@ -409,21 +417,6 @@
             mClingLayout = (ViewGroup)
                     View.inflate(mSysUiContext, R.layout.immersive_mode_cling, null);
 
-            TypedArray ta = mDisplayContext.obtainStyledAttributes(
-                    new int[]{android.R.attr.colorAccent});
-            int colorAccent = ta.getColor(0, 0);
-            ta.recycle();
-            mClingLayout.setBackgroundColor(colorAccent);
-            ImageView expandMore = mClingLayout.findViewById(R.id.immersive_cling_ic_expand_more);
-            if (expandMore != null) {
-                expandMore.setImageTintList(ColorStateList.valueOf(colorAccent));
-            }
-            ImageView lightBgCirc = mClingLayout.findViewById(R.id.immersive_cling_back_bg_light);
-            if (lightBgCirc != null) {
-                // Set transparency to 50%
-                lightBgCirc.setImageAlpha(128);
-            }
-
             final Button ok = mClingLayout.findViewById(R.id.ok);
             ok.setOnClickListener(new OnClickListener() {
                 @Override
@@ -482,6 +475,24 @@
 
         @Override
         public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+            // If the top display cutout overlaps with the full-width (windowWidth=-1)/centered
+            // dialog, then adjust the dialog contents by the cutout
+            final int width = getWidth();
+            final int windowWidth = getClingWindowWidth();
+            final Rect topDisplayCutout = insets.getDisplayCutout() != null
+                    ? insets.getDisplayCutout().getBoundingRectTop()
+                    : new Rect();
+            final boolean intersectsTopCutout = topDisplayCutout.intersects(
+                    width - (windowWidth / 2), 0,
+                    width + (windowWidth / 2), topDisplayCutout.bottom);
+            if (mClingWindow != null &&
+                    (windowWidth < 0 || (width > 0 && intersectsTopCutout))) {
+                final View iconView = mClingWindow.findViewById(R.id.immersive_cling_icon);
+                RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)
+                        iconView.getLayoutParams();
+                lp.topMargin = topDisplayCutout.bottom;
+                iconView.setLayoutParams(lp);
+            }
             // we will be hiding the nav bar, so layout as if it's already hidden
             return new WindowInsets.Builder(insets).setInsets(
                     Type.systemBars(), Insets.NONE).build();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index 6611434..f6fbe38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -30,8 +30,8 @@
 import com.android.systemui.statusbar.chips.domain.interactor.OngoingActivityChipInteractor.Companion.createDialogLaunchOnClickListener
 import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndCastToOtherDeviceDialogDelegate
+import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
 import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndShareToAppDialogDelegate
-import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.Utils
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -60,8 +60,8 @@
     private val mediaProjectionRepository: MediaProjectionRepository,
     private val packageManager: PackageManager,
     private val systemClock: SystemClock,
-    private val dialogFactory: SystemUIDialog.Factory,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
+    private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
 ) : OngoingActivityChipInteractor {
     override val chip: StateFlow<OngoingActivityChipModel> =
         mediaProjectionRepository.mediaProjectionState
@@ -70,9 +70,9 @@
                     is MediaProjectionState.NotProjecting -> OngoingActivityChipModel.Hidden
                     is MediaProjectionState.Projecting -> {
                         if (isProjectionToOtherDevice(state.hostPackage)) {
-                            createCastToOtherDeviceChip()
+                            createCastToOtherDeviceChip(state)
                         } else {
-                            createShareToAppChip()
+                            createShareToAppChip(state)
                         }
                     }
                 }
@@ -97,7 +97,9 @@
         return Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName)
     }
 
-    private fun createCastToOtherDeviceChip(): OngoingActivityChipModel.Shown {
+    private fun createCastToOtherDeviceChip(
+        state: MediaProjectionState.Projecting,
+    ): OngoingActivityChipModel.Shown {
         return OngoingActivityChipModel.Shown(
             icon =
                 Icon.Resource(
@@ -107,32 +109,39 @@
             // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
             startTimeMs = systemClock.elapsedRealtime(),
             createDialogLaunchOnClickListener(
-                castToOtherDeviceDialogDelegate,
+                createCastToOtherDeviceDialogDelegate(state),
                 dialogTransitionAnimator,
             ),
         )
     }
 
-    private val castToOtherDeviceDialogDelegate =
+    private fun createCastToOtherDeviceDialogDelegate(state: MediaProjectionState.Projecting) =
         EndCastToOtherDeviceDialogDelegate(
-            dialogFactory,
+            endMediaProjectionDialogHelper,
             this@MediaProjectionChipInteractor,
+            state,
         )
 
-    private fun createShareToAppChip(): OngoingActivityChipModel.Shown {
+    private fun createShareToAppChip(
+        state: MediaProjectionState.Projecting,
+    ): OngoingActivityChipModel.Shown {
         return OngoingActivityChipModel.Shown(
             // TODO(b/332662551): Use the right content description.
             icon = Icon.Resource(SHARE_TO_APP_ICON, contentDescription = null),
             // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
             startTimeMs = systemClock.elapsedRealtime(),
-            createDialogLaunchOnClickListener(shareToAppDialogDelegate, dialogTransitionAnimator),
+            createDialogLaunchOnClickListener(
+                createShareToAppDialogDelegate(state),
+                dialogTransitionAnimator
+            ),
         )
     }
 
-    private val shareToAppDialogDelegate =
+    private fun createShareToAppDialogDelegate(state: MediaProjectionState.Projecting) =
         EndShareToAppDialogDelegate(
-            dialogFactory,
+            endMediaProjectionDialogHelper,
             this@MediaProjectionChipInteractor,
+            state,
         )
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegate.kt
index 33cec97..596fbf8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegate.kt
@@ -17,25 +17,33 @@
 package com.android.systemui.statusbar.chips.mediaprojection.ui.view
 
 import android.os.Bundle
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialog
 
 /** A dialog that lets the user stop an ongoing cast-screen-to-other-device event. */
 class EndCastToOtherDeviceDialogDelegate(
-    private val systemUIDialogFactory: SystemUIDialog.Factory,
+    private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
     private val interactor: MediaProjectionChipInteractor,
+    private val state: MediaProjectionState.Projecting,
 ) : SystemUIDialog.Delegate {
     override fun createDialog(): SystemUIDialog {
-        return systemUIDialogFactory.create(this)
+        return endMediaProjectionDialogHelper.createDialog(this)
     }
 
     override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
         with(dialog) {
             setIcon(MediaProjectionChipInteractor.CAST_TO_OTHER_DEVICE_ICON)
             setTitle(R.string.cast_to_other_device_stop_dialog_title)
-            // TODO(b/332662551): Use a different message if they're sharing just a single app.
-            setMessage(R.string.cast_to_other_device_stop_dialog_message)
+            setMessage(
+                endMediaProjectionDialogHelper.getDialogMessage(
+                    state,
+                    genericMessageResId = R.string.cast_to_other_device_stop_dialog_message,
+                    specificAppMessageResId =
+                        R.string.cast_to_other_device_stop_dialog_message_specific_app,
+                )
+            )
             // No custom on-click, because the dialog will automatically be dismissed when the
             // button is clicked anyway.
             setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
new file mode 100644
index 0000000..347be02
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 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.chips.mediaprojection.ui.view
+
+import android.annotation.StringRes
+import android.content.Context
+import android.content.pm.PackageManager
+import android.text.Html
+import android.text.Html.FROM_HTML_MODE_LEGACY
+import android.text.TextUtils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import javax.inject.Inject
+
+/** Helper class for showing dialogs that let users end different types of media projections. */
+@SysUISingleton
+class EndMediaProjectionDialogHelper
+@Inject
+constructor(
+    private val dialogFactory: SystemUIDialog.Factory,
+    private val packageManager: PackageManager,
+    private val context: Context
+) {
+    /** Creates a new [SystemUIDialog] using the given delegate. */
+    fun createDialog(delegate: SystemUIDialog.Delegate): SystemUIDialog {
+        return dialogFactory.create(delegate)
+    }
+
+    /**
+     * Returns the message to show in the dialog based on the specific media projection state.
+     *
+     * @param genericMessageResId a res ID for a more generic "end projection" message
+     * @param specificAppMessageResId a res ID for an "end projection" message that also lets us
+     *   specify which app is currently being projected.
+     */
+    fun getDialogMessage(
+        state: MediaProjectionState.Projecting,
+        @StringRes genericMessageResId: Int,
+        @StringRes specificAppMessageResId: Int,
+    ): CharSequence {
+        when (state) {
+            is MediaProjectionState.Projecting.EntireScreen ->
+                return context.getString(genericMessageResId)
+            is MediaProjectionState.Projecting.SingleTask -> {
+                val packageName =
+                    state.task.baseIntent.component?.packageName
+                        ?: return context.getString(genericMessageResId)
+                try {
+                    val appInfo = packageManager.getApplicationInfo(packageName, 0)
+                    val appName = appInfo.loadLabel(packageManager)
+                    return getSpecificAppMessageText(specificAppMessageResId, appName)
+                } catch (e: PackageManager.NameNotFoundException) {
+                    // TODO(b/332662551): Log this error.
+                    return context.getString(genericMessageResId)
+                }
+            }
+        }
+    }
+
+    private fun getSpecificAppMessageText(
+        @StringRes specificAppMessageResId: Int,
+        appName: CharSequence,
+    ): CharSequence {
+        // https://developer.android.com/guide/topics/resources/string-resource#StylingWithHTML
+        val escapedAppName = TextUtils.htmlEncode(appName.toString())
+        val text = context.getString(specificAppMessageResId, escapedAppName)
+        return Html.fromHtml(text, FROM_HTML_MODE_LEGACY)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegate.kt
index 3a863b1..749a11f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegate.kt
@@ -17,25 +17,32 @@
 package com.android.systemui.statusbar.chips.mediaprojection.ui.view
 
 import android.os.Bundle
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialog
 
 /** A dialog that lets the user stop an ongoing share-screen-to-app event. */
 class EndShareToAppDialogDelegate(
-    private val systemUIDialogFactory: SystemUIDialog.Factory,
+    private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
     private val interactor: MediaProjectionChipInteractor,
+    private val state: MediaProjectionState.Projecting,
 ) : SystemUIDialog.Delegate {
     override fun createDialog(): SystemUIDialog {
-        return systemUIDialogFactory.create(this)
+        return endMediaProjectionDialogHelper.createDialog(this)
     }
 
     override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
         with(dialog) {
             setIcon(MediaProjectionChipInteractor.SHARE_TO_APP_ICON)
             setTitle(R.string.share_to_app_stop_dialog_title)
-            // TODO(b/332662551): Use a different message if they're sharing just a single app.
-            setMessage(R.string.share_to_app_stop_dialog_message)
+            setMessage(
+                endMediaProjectionDialogHelper.getDialogMessage(
+                    state,
+                    genericMessageResId = R.string.share_to_app_stop_dialog_message,
+                    specificAppMessageResId = R.string.share_to_app_stop_dialog_message_specific_app
+                )
+            )
             // No custom on-click, because the dialog will automatically be dismissed when the
             // button is clicked anyway.
             setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
index 11636bd..f95e0fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
 import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -224,8 +225,8 @@
                 modifiedStatusBarAttributes,
                 isTransientShown,
                 isInFullscreenMode,
-                ongoingCallRepository.hasOngoingCall,
-            ) { modifiedAttributes, isTransientShown, isInFullscreenMode, hasOngoingCall ->
+                ongoingCallRepository.ongoingCallState,
+            ) { modifiedAttributes, isTransientShown, isInFullscreenMode, ongoingCallState ->
                 if (modifiedAttributes == null) {
                     null
                 } else {
@@ -234,7 +235,7 @@
                             modifiedAttributes.appearance,
                             isTransientShown,
                             isInFullscreenMode,
-                            hasOngoingCall,
+                            hasOngoingCall = ongoingCallState is OngoingCallModel.InCall,
                         )
                     StatusBarAppearance(
                         statusBarMode,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index b13630f..1fc2821 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -486,7 +486,7 @@
                 dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
                 goneToAodTransitionViewModel.notificationAlpha,
                 goneToDreamingTransitionViewModel.lockscreenAlpha,
-                goneToDozingTransitionViewModel.lockscreenAlpha,
+                goneToDozingTransitionViewModel.notificationAlpha,
                 goneToLockscreenTransitionViewModel.lockscreenAlpha,
                 lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
                 lockscreenToGoneTransitionViewModel.notificationAlpha(viewState),
@@ -506,9 +506,7 @@
                 combineTransform(
                     isTransitioningToHiddenKeyguard,
                     alphaForShadeAndQsExpansion,
-                ) {
-                    isTransitioningToHiddenKeyguard,
-                    alphaForShadeAndQsExpansion ->
+                ) { isTransitioningToHiddenKeyguard, alphaForShadeAndQsExpansion ->
                     if (!isTransitioningToHiddenKeyguard) {
                         emit(alphaForShadeAndQsExpansion)
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 2ab7aa9..fae0a46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -85,6 +85,26 @@
         )
     }
 
+    override fun startPendingIntentWithoutDismissing(
+        intent: PendingIntent,
+        dismissShade: Boolean,
+        intentSentUiThreadCallback: Runnable?,
+        animationController: ActivityTransitionAnimator.Controller?,
+        fillInIntent: Intent?,
+        extraOptions: Bundle?
+    ) {
+        activityStarterInternal.startPendingIntentDismissingKeyguard(
+            intent = intent,
+            intentSentUiThreadCallback = intentSentUiThreadCallback,
+            animationController = animationController,
+            showOverLockscreen = true,
+            skipLockscreenChecks = true,
+            dismissShade = dismissShade,
+            fillInIntent = fillInIntent,
+            extraOptions = extraOptions,
+        )
+    }
+
     override fun startPendingIntentMaybeDismissingKeyguard(
         intent: PendingIntent,
         intentSentUiThreadCallback: Runnable?,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
index c9becb4..cff9f5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
@@ -39,6 +39,7 @@
         associatedView: View? = null,
         animationController: ActivityTransitionAnimator.Controller? = null,
         showOverLockscreen: Boolean = false,
+        skipLockscreenChecks: Boolean = false,
         fillInIntent: Intent? = null,
         extraOptions: Bundle? = null,
     )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
index e580f64..dbb95e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
@@ -40,6 +40,7 @@
         associatedView: View?,
         animationController: ActivityTransitionAnimator.Controller?,
         showOverLockscreen: Boolean,
+        skipLockscreenChecks: Boolean,
         fillInIntent: Intent?,
         extraOptions: Bundle?
     ) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index f83aed8..97b6f95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -538,6 +538,9 @@
                     // later to awaken.
                 }
                 mNotificationShadeWindowController.setNotificationShadeFocusable(false);
+                // Notify the interactor first, to prevent race conditions with the screen waking up
+                // that would show a flicker of the lockscreen on DOZING->GONE
+                mBiometricUnlockInteractor.setBiometricUnlockState(mode, biometricUnlockSource);
                 mKeyguardViewMediator.onWakeAndUnlocking(wakeInKeyguard);
                 Trace.endSection();
                 break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 4bf122d..3925beb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -202,6 +202,7 @@
      * Gets the touchable region needed for heads up notifications. Returns null if no touchable
      * region is required (ie: no heads up notification currently exists).
      */
+    // TODO(b/347007367): With scene container enabled this method may report outdated regions
     @Override
     public @Nullable Region getTouchableRegion() {
         NotificationEntry topEntry = getTopEntry();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index 800d7fd..e400ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -228,6 +228,7 @@
         associatedView: View?,
         animationController: ActivityTransitionAnimator.Controller?,
         showOverLockscreen: Boolean,
+        skipLockscreenChecks: Boolean,
         fillInIntent: Intent?,
         extraOptions: Bundle?,
     ) {
@@ -246,10 +247,10 @@
         val actuallyShowOverLockscreen =
             showOverLockscreen &&
                 intent.isActivity &&
-                activityIntentHelper.wouldPendingShowOverLockscreen(
+                    (skipLockscreenChecks || activityIntentHelper.wouldPendingShowOverLockscreen(
                     intent,
                     lockScreenUserManager.currentUserId
-                )
+                    ))
 
         val animate =
             !willLaunchResolverActivity &&
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index d1189e1..4b1ee58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -29,6 +29,7 @@
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
 import android.view.WindowInsets;
 
+import com.android.compose.animation.scene.ObservableTransitionState;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.ScreenDecorations;
@@ -38,7 +39,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -67,7 +68,7 @@
     private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
 
     private boolean mIsStatusBarExpanded = false;
-    private boolean mIsSceneContainerVisible = false;
+    private boolean mIsIdleOnGone = false;
     private boolean mShouldAdjustInsets = false;
     private View mNotificationShadeWindowView;
     private View mNotificationPanelView;
@@ -87,7 +88,6 @@
             NotificationShadeWindowController notificationShadeWindowController,
             ConfigurationController configurationController,
             HeadsUpManager headsUpManager,
-            ShadeExpansionStateManager shadeExpansionStateManager,
             ShadeInteractor shadeInteractor,
             Provider<SceneInteractor> sceneInteractor,
             JavaAdapter javaAdapter,
@@ -131,8 +131,8 @@
 
         if (SceneContainerFlag.isEnabled()) {
             javaAdapter.alwaysCollectFlow(
-                    sceneInteractor.get().isVisible(),
-                    this::onSceneContainerVisibilityChanged);
+                    sceneInteractor.get().getTransitionState(),
+                    this::onSceneChanged);
         } else {
             javaAdapter.alwaysCollectFlow(
                     shadeInteractor.isAnyExpanded(),
@@ -167,10 +167,11 @@
         }
     }
 
-    private void onSceneContainerVisibilityChanged(Boolean isVisible) {
-        if (isVisible != mIsSceneContainerVisible) {
-            mIsSceneContainerVisible = isVisible;
-            if (isVisible) {
+    private void onSceneChanged(ObservableTransitionState transitionState) {
+        boolean isIdleOnGone = transitionState.isIdle(Scenes.Gone);
+        if (isIdleOnGone != mIsIdleOnGone) {
+            mIsIdleOnGone = isIdleOnGone;
+            if (!isIdleOnGone) {
                 // make sure our state is sensible
                 mForceCollapsedUntilLayout = false;
             }
@@ -281,7 +282,7 @@
         // since we don't want stray touches to go through the light reveal scrim to whatever is
         // underneath.
         return mIsStatusBarExpanded
-                || mIsSceneContainerVisible
+                || !mIsIdleOnGone
                 || mPrimaryBouncerInteractor.isShowing().getValue()
                 || mAlternateBouncerInteractor.isVisibleState()
                 || mUnlockedScreenOffAnimationController.isAnimationPlaying();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index a7d4ce3..d128057 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -29,35 +29,36 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.CoreStartable
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
-/**
- * A controller to handle the ongoing call chip in the collapsed status bar.
- */
+/** A controller to handle the ongoing call chip in the collapsed status bar. */
 @SysUISingleton
-class OngoingCallController @Inject constructor(
+class OngoingCallController
+@Inject
+constructor(
     @Application private val scope: CoroutineScope,
     private val context: Context,
     private val ongoingCallRepository: OngoingCallRepository,
@@ -79,54 +80,61 @@
 
     private val mListeners: MutableList<OngoingCallListener> = mutableListOf()
     private val uidObserver = CallAppUidObserver()
-    private val notifListener = object : NotifCollectionListener {
-        // Temporary workaround for b/178406514 for testing purposes.
-        //
-        // b/178406514 means that posting an incoming call notif then updating it to an ongoing call
-        // notif does not work (SysUI never receives the update). This workaround allows us to
-        // trigger the ongoing call chip when an ongoing call notif is *added* rather than
-        // *updated*, allowing us to test the chip.
-        //
-        // TODO(b/183229367): Remove this function override when b/178406514 is fixed.
-        override fun onEntryAdded(entry: NotificationEntry) {
-            onEntryUpdated(entry, true)
-        }
+    private val notifListener =
+        object : NotifCollectionListener {
+            // Temporary workaround for b/178406514 for testing purposes.
+            //
+            // b/178406514 means that posting an incoming call notif then updating it to an ongoing
+            // call notif does not work (SysUI never receives the update). This workaround allows us
+            // to trigger the ongoing call chip when an ongoing call notif is *added* rather than
+            // *updated*, allowing us to test the chip.
+            //
+            // TODO(b/183229367): Remove this function override when b/178406514 is fixed.
+            override fun onEntryAdded(entry: NotificationEntry) {
+                onEntryUpdated(entry, true)
+            }
 
-        override fun onEntryUpdated(entry: NotificationEntry) {
-            // We have a new call notification or our existing call notification has been updated.
-            // TODO(b/183229367): This likely won't work if you take a call from one app then
-            //  switch to a call from another app.
-            if (callNotificationInfo == null && isCallNotification(entry) ||
-                    (entry.sbn.key == callNotificationInfo?.key)) {
-                val newOngoingCallInfo = CallNotificationInfo(
-                        entry.sbn.key,
-                        entry.sbn.notification.getWhen(),
-                        entry.sbn.notification.contentIntent,
-                        entry.sbn.uid,
-                        entry.sbn.notification.extras.getInt(
-                                Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING,
-                        statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
-                )
-                if (newOngoingCallInfo == callNotificationInfo) {
-                    return
+            override fun onEntryUpdated(entry: NotificationEntry) {
+                // We have a new call notification or our existing call notification has been
+                // updated.
+                // TODO(b/183229367): This likely won't work if you take a call from one app then
+                //  switch to a call from another app.
+                if (
+                    callNotificationInfo == null && isCallNotification(entry) ||
+                        (entry.sbn.key == callNotificationInfo?.key)
+                ) {
+                    val newOngoingCallInfo =
+                        CallNotificationInfo(
+                            entry.sbn.key,
+                            entry.sbn.notification.getWhen(),
+                            entry.sbn.notification.contentIntent,
+                            entry.sbn.uid,
+                            entry.sbn.notification.extras.getInt(
+                                Notification.EXTRA_CALL_TYPE,
+                                -1
+                            ) == CALL_TYPE_ONGOING,
+                            statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
+                        )
+                    if (newOngoingCallInfo == callNotificationInfo) {
+                        return
+                    }
+
+                    callNotificationInfo = newOngoingCallInfo
+                    if (newOngoingCallInfo.isOngoing) {
+                        updateChip()
+                    } else {
+                        removeChip()
+                    }
                 }
+            }
 
-                callNotificationInfo = newOngoingCallInfo
-                if (newOngoingCallInfo.isOngoing) {
-                    updateChip()
-                } else {
+            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+                if (entry.sbn.key == callNotificationInfo?.key) {
                     removeChip()
                 }
             }
         }
 
-        override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
-            if (entry.sbn.key == callNotificationInfo?.key) {
-                removeChip()
-            }
-        }
-    }
-
     override fun start() {
         dumpManager.registerDumpable(this)
         notifCollection.addCollectionListener(notifListener)
@@ -169,8 +177,21 @@
      */
     fun hasOngoingCall(): Boolean {
         return callNotificationInfo?.isOngoing == true &&
-                // When the user is in the phone app, don't show the chip.
-                !uidObserver.isCallAppVisible
+            // When the user is in the phone app, don't show the chip.
+            !uidObserver.isCallAppVisible
+    }
+
+    /** Creates the right [OngoingCallModel] based on the call state. */
+    private fun getOngoingCallModel(): OngoingCallModel {
+        if (hasOngoingCall()) {
+            val currentInfo =
+                callNotificationInfo
+                    // This shouldn't happen, but protect against it in case
+                    ?: return OngoingCallModel.NoCall
+            return OngoingCallModel.InCall(currentInfo.callStartTime)
+        } else {
+            return OngoingCallModel.NoCall
+        }
     }
 
     override fun addCallback(listener: OngoingCallListener) {
@@ -182,9 +203,7 @@
     }
 
     override fun removeCallback(listener: OngoingCallListener) {
-        synchronized(mListeners) {
-            mListeners.remove(listener)
-        }
+        synchronized(mListeners) { mListeners.remove(listener) }
     }
 
     private fun updateChip() {
@@ -196,8 +215,8 @@
         if (currentChipView != null && timeView != null) {
             if (currentCallNotificationInfo.hasValidStartTime()) {
                 timeView.setShouldHideText(false)
-                timeView.base = currentCallNotificationInfo.callStartTime -
-                        systemClock.currentTimeMillis() +
+                timeView.base =
+                    currentCallNotificationInfo.callStartTime - systemClock.currentTimeMillis() +
                         systemClock.elapsedRealtime()
                 timeView.start()
             } else {
@@ -218,14 +237,19 @@
             callNotificationInfo = null
 
             if (DEBUG) {
-                Log.w(TAG, "Ongoing call chip view could not be found; " +
-                        "Not displaying chip in status bar")
+                Log.w(
+                    TAG,
+                    "Ongoing call chip view could not be found; " +
+                        "Not displaying chip in status bar"
+                )
             }
         }
     }
 
     private fun updateChipClickListener() {
-        if (callNotificationInfo == null) { return }
+        if (callNotificationInfo == null) {
+            return
+        }
         val currentChipView = chipView
         val backgroundView =
             currentChipView?.findViewById<View>(R.id.ongoing_activity_chip_background)
@@ -237,7 +261,8 @@
                     intent,
                     ActivityTransitionAnimator.Controller.fromView(
                         backgroundView,
-                        InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
+                        InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+                    )
                 )
             }
         }
@@ -249,9 +274,11 @@
     }
 
     private fun updateGestureListening() {
-        if (callNotificationInfo == null ||
-            callNotificationInfo?.statusBarSwipedAway == true ||
-            !isFullscreen) {
+        if (
+            callNotificationInfo == null ||
+                callNotificationInfo?.statusBarSwipedAway == true ||
+                !isFullscreen
+        ) {
             swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
         } else {
             swipeStatusBarAwayGestureHandler.addOnGestureDetectedCallback(TAG) { _ ->
@@ -270,30 +297,31 @@
     }
 
     /** Tear down anything related to the chip view to prevent leaks. */
-    @VisibleForTesting
-    fun tearDownChipView() = chipView?.getTimeView()?.stop()
+    @VisibleForTesting fun tearDownChipView() = chipView?.getTimeView()?.stop()
 
     private fun View.getTimeView(): ChipChronometer? {
         return this.findViewById(R.id.ongoing_activity_chip_time)
     }
 
     /**
-    * If there's an active ongoing call, then we will force the status bar to always show, even if
-    * the user is in immersive mode. However, we also want to give users the ability to swipe away
-    * the status bar if they need to access the area under the status bar.
-    *
-    * This method updates the status bar window appropriately when the swipe away gesture is
-    * detected.
-    */
+     * If there's an active ongoing call, then we will force the status bar to always show, even if
+     * the user is in immersive mode. However, we also want to give users the ability to swipe away
+     * the status bar if they need to access the area under the status bar.
+     *
+     * This method updates the status bar window appropriately when the swipe away gesture is
+     * detected.
+     */
     private fun onSwipeAwayGestureDetected() {
-        if (DEBUG) { Log.d(TAG, "Swipe away gesture detected") }
+        if (DEBUG) {
+            Log.d(TAG, "Swipe away gesture detected")
+        }
         callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
         statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(false)
         swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
     }
 
     private fun sendStateChangeEvent() {
-        ongoingCallRepository.setHasOngoingCall(hasOngoingCall())
+        ongoingCallRepository.setOngoingCallState(getOngoingCallModel())
         mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
     }
 
@@ -308,8 +336,8 @@
         val statusBarSwipedAway: Boolean
     ) {
         /**
-         * Returns true if the notification information has a valid call start time.
-         * See b/192379214.
+         * Returns true if the notification information has a valid call start time. See
+         * b/192379214.
          */
         fun hasValidStartTime(): Boolean = callStartTime > 0
     }
@@ -342,9 +370,10 @@
             callAppUid = uid
 
             try {
-                isCallAppVisible = isProcessVisibleToUser(
-                    iActivityManager.getUidProcessState(uid, context.opPackageName)
-                )
+                isCallAppVisible =
+                    isProcessVisibleToUser(
+                        iActivityManager.getUidProcessState(uid, context.opPackageName)
+                    )
                 if (isRegistered) {
                     return
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/model/OngoingCallModel.kt
new file mode 100644
index 0000000..aaa52a7b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/model/OngoingCallModel.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.ongoingcall.data.model
+
+/** Represents the state of any ongoing calls. */
+sealed interface OngoingCallModel {
+    /** There is no ongoing call. */
+    data object NoCall : OngoingCallModel
+
+    /**
+     * There *is* an ongoing call.
+     *
+     * @property startTimeMs the time that the phone call started, based on the notification's
+     *   `when` field. Importantly, this time is relative to
+     *   [com.android.systemui.util.time.SystemClock.currentTimeMillis], **not**
+     *   [com.android.systemui.util.time.SystemClock.elapsedRealtime].
+     */
+    data class InCall(val startTimeMs: Long) : OngoingCallModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
index 886481e..554c474 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone.ongoingcall.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -32,15 +33,15 @@
  */
 @SysUISingleton
 class OngoingCallRepository @Inject constructor() {
-    private val _hasOngoingCall = MutableStateFlow(false)
-    /** True if there's currently an ongoing call notification and false otherwise. */
-    val hasOngoingCall: StateFlow<Boolean> = _hasOngoingCall.asStateFlow()
+    private val _ongoingCallState = MutableStateFlow<OngoingCallModel>(OngoingCallModel.NoCall)
+    /** The current ongoing call state. */
+    val ongoingCallState: StateFlow<OngoingCallModel> = _ongoingCallState.asStateFlow()
 
     /**
-     * Sets whether there's currently an ongoing call notification. Should only be set from
+     * Sets the current ongoing call state, based on notifications. Should only be set from
      * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController].
      */
-    fun setHasOngoingCall(hasOngoingCall: Boolean) {
-        _hasOngoingCall.value = hasOngoingCall
+    fun setOngoingCallState(state: OngoingCallModel) {
+        _ongoingCallState.value = state
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
index f4e3eab..0871c86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
@@ -18,6 +18,7 @@
 
 import android.os.PersistableBundle
 import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
+import android.telephony.CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL
 import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
 import androidx.annotation.VisibleForTesting
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -66,10 +67,16 @@
     /** Flow tracking the [KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL] config */
     val showOperatorNameInStatusBar: StateFlow<Boolean> = showOperatorName.config
 
+    private val showNetworkSlice =
+        BooleanCarrierConfig(KEY_SHOW_5G_SLICE_ICON_BOOL, defaultConfig)
+    /** Flow tracking the [KEY_SHOW_5G_SLICE_ICON_BOOL] config */
+    val allowNetworkSliceIndicator: StateFlow<Boolean> = showNetworkSlice.config
+
     private val trackedConfigs =
         listOf(
             inflateSignalStrength,
             showOperatorName,
+            showNetworkSlice,
         )
 
     /** Ingest a new carrier config, and switch all of the tracked keys over to the new values */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 425c58b..205205e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -48,6 +48,9 @@
     /** Reflects the value from the carrier config INFLATE_SIGNAL_STRENGTH for this connection */
     val inflateSignalStrength: StateFlow<Boolean>
 
+    /** Carrier config KEY_SHOW_5G_SLICE_ICON_BOOL for this connection */
+    val allowNetworkSliceIndicator: StateFlow<Boolean>
+
     /**
      * The table log buffer created for this connection. Will have the name "MobileConnectionLog
      * [subId]"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index 83d5f2b..3261b71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -79,6 +79,9 @@
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), _inflateSignalStrength.value)
 
+    // I don't see a reason why we would turn the config off for demo mode.
+    override val allowNetworkSliceIndicator = MutableStateFlow(true)
+
     private val _isEmergencyOnly = MutableStateFlow(false)
     override val isEmergencyOnly =
         _isEmergencyOnly
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index a532e62..2e47678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -166,6 +166,7 @@
     override val isRoaming = MutableStateFlow(false).asStateFlow()
     override val carrierId = MutableStateFlow(INVALID_SUBSCRIPTION_ID).asStateFlow()
     override val inflateSignalStrength = MutableStateFlow(false).asStateFlow()
+    override val allowNetworkSliceIndicator = MutableStateFlow(false).asStateFlow()
     override val isEmergencyOnly = MutableStateFlow(false).asStateFlow()
     override val operatorAlphaShort = MutableStateFlow(null).asStateFlow()
     override val isInService = MutableStateFlow(true).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index 41559b2..a5e47a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -308,6 +308,21 @@
                 activeRepo.value.inflateSignalStrength.value
             )
 
+    override val allowNetworkSliceIndicator =
+        activeRepo
+            .flatMapLatest { it.allowNetworkSliceIndicator }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = "allowSlice",
+                initialValue = activeRepo.value.allowNetworkSliceIndicator.value,
+            )
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                activeRepo.value.allowNetworkSliceIndicator.value
+            )
+
     override val numberOfLevels =
         activeRepo
             .flatMapLatest { it.numberOfLevels }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 6803a9d..9449659 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -310,6 +310,7 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), UnknownNetworkType)
 
     override val inflateSignalStrength = systemUiCarrierConfig.shouldInflateSignalStrength
+    override val allowNetworkSliceIndicator = systemUiCarrierConfig.allowNetworkSliceIndicator
 
     override val numberOfLevels =
         inflateSignalStrength
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index ed9e405..507759c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -253,7 +253,13 @@
             )
 
     override val showSliceAttribution: StateFlow<Boolean> =
-        connectionRepository.hasPrioritizedNetworkCapabilities
+        combine(
+                connectionRepository.allowNetworkSliceIndicator,
+                connectionRepository.hasPrioritizedNetworkCapabilities,
+            ) { allowed, hasPrioritizedNetworkCapabilities ->
+                allowed && hasPrioritizedNetworkCapabilities
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val isNonTerrestrial: StateFlow<Boolean> =
         if (Flags.carrierEnabledSatelliteFlag()) {
@@ -350,7 +356,8 @@
         shownLevel.map {
             SignalIconModel.Satellite(
                 level = it,
-                icon = SatelliteIconModel.fromSignalStrength(it)
+                icon =
+                    SatelliteIconModel.fromSignalStrength(it)
                         ?: SatelliteIconModel.fromSignalStrength(0)!!
             )
         }
diff --git a/packages/SystemUI/tests/goldens/doubleClick_swapSide.json b/packages/SystemUI/tests/goldens/doubleClick_swapSide.json
index 044ddbc..c4998a5 100644
--- a/packages/SystemUI/tests/goldens/doubleClick_swapSide.json
+++ b/packages/SystemUI/tests/goldens/doubleClick_swapSide.json
@@ -38,7 +38,7 @@
           "y": 96
         },
         {
-          "x": 45.008995,
+          "x": 45.009,
           "y": 96
         },
         {
@@ -133,7 +133,7 @@
           "y": 96
         },
         {
-          "x": 156.13857,
+          "x": 156.13858,
           "y": 96
         },
         {
@@ -141,7 +141,7 @@
           "y": 96
         },
         {
-          "x": 64.81257,
+          "x": 64.81259,
           "y": 96
         },
         {
@@ -149,11 +149,11 @@
           "y": 96
         },
         {
-          "x": 24.443243,
+          "x": 24.443266,
           "y": 96
         },
         {
-          "x": 14.680362,
+          "x": 14.680339,
           "y": 96
         },
         {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index d267ad4..54a14a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -39,6 +39,7 @@
 import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -1182,6 +1183,24 @@
     }
 
     @Test
+    public void testUpdateOverlayProviderViews_PendingConfigChange() {
+        final DecorProvider cutout = new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP);
+        spyOn(cutout);
+        doNothing().when(cutout).onReloadResAndMeasure(any(), anyInt(), anyInt(), anyInt(), any());
+        mMockCutoutList.add(cutout);
+        mScreenDecorations.start();
+        doCallRealMethod().when(mScreenDecorations).updateOverlayProviderViews(any());
+
+        mScreenDecorations.mPendingConfigChange = true;
+        mScreenDecorations.updateOverlayProviderViews(null /* filterIds */);
+        verify(cutout, never()).onReloadResAndMeasure(any(), anyInt(), anyInt(), anyInt(), any());
+
+        mScreenDecorations.mPendingConfigChange = false;
+        mScreenDecorations.updateOverlayProviderViews(null /* filterIds */);
+        verify(cutout).onReloadResAndMeasure(any(), anyInt(), anyInt(), anyInt(), any());
+    }
+
+    @Test
     public void testSupportHwcLayer_SwitchFrom_NotSupport() {
         setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 128dd23..27b9863 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -44,6 +44,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.clearInvocations
 import java.util.function.Predicate
 
 @RunWith(AndroidJUnit4::class)
@@ -336,6 +337,10 @@
                 false /* requestedShowSurfaceBehindKeyguard */
         )
 
+        // Cancel the animator so we can verify only the setSurfaceBehind call below.
+        keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.end()
+        clearInvocations(surfaceTransactionApplier)
+
         // Set appear to 50%, we'll just verify that we're not applying the identity matrix which
         // means an animation is in progress.
         keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(0.5f)
@@ -377,6 +382,10 @@
                 false /* requestedShowSurfaceBehindKeyguard */
         )
 
+        // Cancel the animator so we can verify only the setSurfaceBehind call below.
+        keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.end()
+        clearInvocations(surfaceTransactionApplier)
+
         keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(1f)
         keyguardUnlockAnimationController.setWallpaperAppearAmount(1f)
 
@@ -409,6 +418,10 @@
                 false /* requestedShowSurfaceBehindKeyguard */
         )
 
+        // Stop the animator - we just want to test whether the override is not applied.
+        keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.end()
+        clearInvocations(surfaceTransactionApplier)
+
         keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(1f)
         keyguardUnlockAnimationController.setWallpaperAppearAmount(1f)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 6b1d39a..03afcb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -1240,6 +1240,7 @@
         mViewMediator.start();
 
         mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null);
+        mViewMediator.onBootCompleted();
     }
 
     private void captureKeyguardStateControllerCallback() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 89e0971..246cfbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -569,13 +569,13 @@
             // WHEN biometrics succeeds with wake and unlock mode
             powerInteractor.setAwakeForTest()
             keyguardRepository.setBiometricUnlockState(BiometricUnlockMode.WAKE_AND_UNLOCK)
-            advanceTimeBy(60L)
+            runCurrent()
 
             assertThat(transitionRepository)
                 .startedTransition(
                     to = KeyguardState.GONE,
                     from = KeyguardState.DOZING,
-                    ownerName = "FromDozingTransitionInteractor",
+                    ownerName = "FromDozingTransitionInteractor(biometric wake and unlock)",
                     animatorAssertion = { it.isNotNull() }
                 )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index bba01bd..6c350cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -25,6 +25,8 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardViewController
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
 import com.android.systemui.dreams.DreamOverlayStateController
@@ -498,6 +500,8 @@
                 to = KeyguardState.GLANCEABLE_HUB,
                 testScope = testScope,
             )
+            kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal)
+            runCurrent()
             mediaHierarchyManager.qsExpansion = 0f
             mediaHierarchyManager.setTransitionToFullShadeAmount(123f)
 
@@ -542,6 +546,8 @@
                 to = KeyguardState.GLANCEABLE_HUB,
                 testScope = testScope,
             )
+            kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal)
+            runCurrent()
             verify(mediaCarouselController)
                 .onDesiredLocationChanged(
                     eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
@@ -557,6 +563,8 @@
                 to = KeyguardState.LOCKSCREEN,
                 testScope = testScope,
             )
+            kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Blank)
+            runCurrent()
             verify(mediaCarouselController)
                 .onDesiredLocationChanged(
                     eq(MediaHierarchyManager.LOCATION_QQS),
@@ -579,6 +587,8 @@
                 to = KeyguardState.GLANCEABLE_HUB,
                 testScope = testScope,
             )
+            kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal)
+            runCurrent()
             verify(mediaCarouselController)
                 .onDesiredLocationChanged(
                     eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
@@ -600,6 +610,8 @@
                 to = KeyguardState.GLANCEABLE_HUB,
                 testScope = testScope,
             )
+            kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal)
+            runCurrent()
             verify(mediaCarouselController)
                 .onDesiredLocationChanged(
                     eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
@@ -635,6 +647,8 @@
                 to = KeyguardState.GLANCEABLE_HUB,
                 testScope = testScope,
             )
+            kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal)
+            runCurrent()
             // Mock the behavior for dreaming that pulling down shade will immediately set QS as
             // expanded
             expandQS()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
index a4505a9..327eec4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
@@ -55,7 +56,7 @@
 
 @SmallTest
 class MediaProjectionChipInteractorTest : SysuiTestCase() {
-    private val kosmos = Kosmos()
+    private val kosmos = Kosmos().also { it.testCase = this }
     private val testScope = kosmos.testScope
     private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository
     private val systemClock = kosmos.fakeSystemClock
@@ -178,7 +179,7 @@
         }
 
     @Test
-    fun chip_castToOtherDevice_clickListenerShowsCastDialog() =
+    fun chip_castToOtherDevice_entireScreen_clickListenerShowsCastDialog() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
             mediaProjectionRepo.mediaProjectionState.value =
@@ -200,7 +201,33 @@
         }
 
     @Test
-    fun chip_shareToApp_clickListenerShowsShareDialog() =
+    fun chip_castToOtherDevice_singleTask_clickListenerShowsCastDialog() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.SingleTask(
+                    CAST_TO_OTHER_DEVICES_PACKAGE,
+                    createTask(taskId = 1)
+                )
+
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+
+            // Dialogs must be created on the main thread
+            context.mainExecutor.execute {
+                clickListener.onClick(chipView)
+                verify(kosmos.mockDialogTransitionAnimator)
+                    .showFromView(
+                        eq(mockCastDialog),
+                        eq(chipBackgroundView),
+                        eq(null),
+                        anyBoolean(),
+                    )
+            }
+        }
+
+    @Test
+    fun chip_shareToApp_entireScreen_clickListenerShowsShareDialog() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
             mediaProjectionRepo.mediaProjectionState.value =
@@ -221,6 +248,28 @@
             }
         }
 
+    @Test
+    fun chip_shareToApp_singleTask_clickListenerShowsShareDialog() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.SingleTask(NORMAL_PACKAGE, createTask(taskId = 1))
+
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+
+            // Dialogs must be created on the main thread
+            context.mainExecutor.execute {
+                clickListener.onClick(chipView)
+                verify(kosmos.mockDialogTransitionAnimator)
+                    .showFromView(
+                        eq(mockShareDialog),
+                        eq(chipBackgroundView),
+                        eq(null),
+                        anyBoolean(),
+                    )
+            }
+        }
+
     companion object {
         const val CAST_TO_OTHER_DEVICES_PACKAGE = "other.devices.package"
         const val NORMAL_PACKAGE = "some.normal.package"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt
index 9a2f545..7b676e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt
@@ -16,22 +16,28 @@
 
 package com.android.systemui.statusbar.chips.mediaprojection.ui.view
 
+import android.content.ComponentName
 import android.content.DialogInterface
+import android.content.Intent
+import android.content.packageManager
+import android.content.pm.ApplicationInfo
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
+import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
@@ -41,22 +47,14 @@
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
 class EndCastToOtherDeviceDialogDelegateTest : SysuiTestCase() {
-    private val kosmos = Kosmos()
+    private val kosmos = Kosmos().also { it.testCase = this }
     private val sysuiDialog = mock<SystemUIDialog>()
-    private val sysuiDialogFactory = kosmos.mockSystemUIDialogFactory
-    private val underTest =
-        EndCastToOtherDeviceDialogDelegate(
-            sysuiDialogFactory,
-            kosmos.mediaProjectionChipInteractor,
-        )
-
-    @Before
-    fun setUp() {
-        whenever(sysuiDialogFactory.create(eq(underTest), eq(context))).thenReturn(sysuiDialog)
-    }
+    private lateinit var underTest: EndCastToOtherDeviceDialogDelegate
 
     @Test
     fun icon() {
+        createAndSetDelegate(ENTIRE_SCREEN)
+
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
 
         verify(sysuiDialog).setIcon(R.drawable.ic_cast_connected)
@@ -64,20 +62,52 @@
 
     @Test
     fun title() {
+        createAndSetDelegate(SINGLE_TASK)
+
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
 
         verify(sysuiDialog).setTitle(R.string.cast_to_other_device_stop_dialog_title)
     }
 
     @Test
-    fun message() {
+    fun message_entireScreen() {
+        createAndSetDelegate(ENTIRE_SCREEN)
+
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
 
-        verify(sysuiDialog).setMessage(R.string.cast_to_other_device_stop_dialog_message)
+        verify(sysuiDialog)
+            .setMessage(context.getString(R.string.cast_to_other_device_stop_dialog_message))
+    }
+
+    @Test
+    fun message_singleTask() {
+        val baseIntent =
+            Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+        val appInfo = mock<ApplicationInfo>()
+        whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package")
+        whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+            .thenReturn(appInfo)
+
+        createAndSetDelegate(
+            MediaProjectionState.Projecting.SingleTask(
+                HOST_PACKAGE,
+                createTask(taskId = 1, baseIntent = baseIntent)
+            )
+        )
+
+        underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+        // It'd be nice to use R.string.cast_to_other_device_stop_dialog_message_specific_app
+        // directly, but it includes the <b> tags which aren't in the returned string.
+        val result = argumentCaptor<CharSequence>()
+        verify(sysuiDialog).setMessage(result.capture())
+        assertThat(result.firstValue.toString()).isEqualTo("You will stop casting Fake Package")
     }
 
     @Test
     fun negativeButton() {
+        createAndSetDelegate(ENTIRE_SCREEN)
+
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
 
         verify(sysuiDialog).setNegativeButton(R.string.close_dialog_button, null)
@@ -86,6 +116,8 @@
     @Test
     fun positiveButton() =
         kosmos.testScope.runTest {
+            createAndSetDelegate(SINGLE_TASK)
+
             underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
 
             val clickListener = argumentCaptor<DialogInterface.OnClickListener>()
@@ -105,4 +137,20 @@
 
             assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isTrue()
         }
+
+    private fun createAndSetDelegate(state: MediaProjectionState.Projecting) {
+        underTest =
+            EndCastToOtherDeviceDialogDelegate(
+                kosmos.endMediaProjectionDialogHelper,
+                kosmos.mediaProjectionChipInteractor,
+                state,
+            )
+    }
+
+    companion object {
+        private const val HOST_PACKAGE = "fake.host.package"
+        private val ENTIRE_SCREEN = MediaProjectionState.Projecting.EntireScreen(HOST_PACKAGE)
+        private val SINGLE_TASK =
+            MediaProjectionState.Projecting.SingleTask(HOST_PACKAGE, createTask(taskId = 1))
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
new file mode 100644
index 0000000..bbd1109
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 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.chips.mediaprojection.ui.view
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.packageManager
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+class EndMediaProjectionDialogHelperTest : SysuiTestCase() {
+    private val kosmos = Kosmos().also { it.testCase = this }
+
+    private val underTest = kosmos.endMediaProjectionDialogHelper
+
+    @Test
+    fun createDialog_usesDelegateAndFactory() {
+        val dialog = mock<SystemUIDialog>()
+        val delegate = SystemUIDialog.Delegate { dialog }
+        whenever(kosmos.mockSystemUIDialogFactory.create(eq(delegate))).thenReturn(dialog)
+
+        underTest.createDialog(delegate)
+
+        verify(kosmos.mockSystemUIDialogFactory).create(delegate)
+    }
+
+    @Test
+    fun getDialogMessage_entireScreen_isGenericMessage() {
+        val result =
+            underTest.getDialogMessage(
+                MediaProjectionState.Projecting.EntireScreen("host.package"),
+                R.string.accessibility_home,
+                R.string.cast_to_other_device_stop_dialog_message_specific_app
+            )
+
+        assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
+    }
+
+    @Test
+    fun getDialogMessage_singleTask_cannotFindPackage_isGenericMessage() {
+        val baseIntent =
+            Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+        whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+            .thenThrow(PackageManager.NameNotFoundException())
+
+        val projectionState =
+            MediaProjectionState.Projecting.SingleTask(
+                "host.package",
+                createTask(taskId = 1, baseIntent = baseIntent)
+            )
+
+        val result =
+            underTest.getDialogMessage(
+                projectionState,
+                R.string.accessibility_home,
+                R.string.cast_to_other_device_stop_dialog_message_specific_app
+            )
+
+        assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
+    }
+
+    @Test
+    fun getDialogMessage_singleTask_findsPackage_isSpecificMessageWithAppLabel() {
+        val baseIntent =
+            Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+        val appInfo = mock<ApplicationInfo>()
+        whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package")
+        whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+            .thenReturn(appInfo)
+
+        val projectionState =
+            MediaProjectionState.Projecting.SingleTask(
+                "host.package",
+                createTask(taskId = 1, baseIntent = baseIntent)
+            )
+
+        val result =
+            underTest.getDialogMessage(
+                projectionState,
+                R.string.accessibility_home,
+                R.string.cast_to_other_device_stop_dialog_message_specific_app
+            )
+
+        // It'd be nice to use the R.string resources directly, but they include the <b> tags which
+        // aren't in the returned string.
+        assertThat(result.toString()).isEqualTo("You will stop casting Fake Package")
+    }
+
+    @Test
+    fun getDialogMessage_appLabelHasSpecialCharacters_isEscaped() {
+        val baseIntent =
+            Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+        val appInfo = mock<ApplicationInfo>()
+        whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake & Package <Here>")
+        whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+            .thenReturn(appInfo)
+
+        val projectionState =
+            MediaProjectionState.Projecting.SingleTask(
+                "host.package",
+                createTask(taskId = 1, baseIntent = baseIntent)
+            )
+
+        val result =
+            underTest.getDialogMessage(
+                projectionState,
+                R.string.accessibility_home,
+                R.string.cast_to_other_device_stop_dialog_message_specific_app
+            )
+
+        assertThat(result.toString()).isEqualTo("You will stop casting Fake & Package <Here>")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegateTest.kt
index 1d6e866..4ddca52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndShareToAppDialogDelegateTest.kt
@@ -16,22 +16,28 @@
 
 package com.android.systemui.statusbar.chips.mediaprojection.ui.view
 
+import android.content.ComponentName
 import android.content.DialogInterface
+import android.content.Intent
+import android.content.packageManager
+import android.content.pm.ApplicationInfo
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
+import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
@@ -41,22 +47,14 @@
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
 class EndShareToAppDialogDelegateTest : SysuiTestCase() {
-    private val kosmos = Kosmos()
+    private val kosmos = Kosmos().also { it.testCase = this }
     private val sysuiDialog = mock<SystemUIDialog>()
-    private val sysuiDialogFactory = kosmos.mockSystemUIDialogFactory
-    private val underTest =
-        EndShareToAppDialogDelegate(
-            sysuiDialogFactory,
-            kosmos.mediaProjectionChipInteractor,
-        )
-
-    @Before
-    fun setUp() {
-        whenever(sysuiDialogFactory.create(eq(underTest), eq(context))).thenReturn(sysuiDialog)
-    }
+    private lateinit var underTest: EndShareToAppDialogDelegate
 
     @Test
     fun icon() {
+        createAndSetDelegate(SINGLE_TASK)
+
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
 
         verify(sysuiDialog).setIcon(R.drawable.ic_screenshot_share)
@@ -64,20 +62,51 @@
 
     @Test
     fun title() {
+        createAndSetDelegate(ENTIRE_SCREEN)
+
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
 
         verify(sysuiDialog).setTitle(R.string.share_to_app_stop_dialog_title)
     }
 
     @Test
-    fun message() {
+    fun message_entireScreen() {
+        createAndSetDelegate(ENTIRE_SCREEN)
+
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
 
-        verify(sysuiDialog).setMessage(R.string.share_to_app_stop_dialog_message)
+        verify(sysuiDialog).setMessage(context.getString(R.string.share_to_app_stop_dialog_message))
+    }
+
+    @Test
+    fun message_singleTask() {
+        val baseIntent =
+            Intent().apply { this.component = ComponentName("fake.task.package", "cls") }
+        val appInfo = mock<ApplicationInfo>()
+        whenever(appInfo.loadLabel(kosmos.packageManager)).thenReturn("Fake Package")
+        whenever(kosmos.packageManager.getApplicationInfo(eq("fake.task.package"), any<Int>()))
+            .thenReturn(appInfo)
+
+        createAndSetDelegate(
+            MediaProjectionState.Projecting.SingleTask(
+                HOST_PACKAGE,
+                createTask(taskId = 1, baseIntent = baseIntent)
+            )
+        )
+
+        underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+        // It'd be nice to use R.string.share_to_app_stop_dialog_message_specific_app directly, but
+        // it includes the <b> tags which aren't in the returned string.
+        val result = argumentCaptor<CharSequence>()
+        verify(sysuiDialog).setMessage(result.capture())
+        assertThat(result.firstValue.toString()).isEqualTo("You will stop sharing Fake Package")
     }
 
     @Test
     fun negativeButton() {
+        createAndSetDelegate(SINGLE_TASK)
+
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
 
         verify(sysuiDialog).setNegativeButton(R.string.close_dialog_button, null)
@@ -86,6 +115,8 @@
     @Test
     fun positiveButton() =
         kosmos.testScope.runTest {
+            createAndSetDelegate(ENTIRE_SCREEN)
+
             underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
 
             val clickListener = argumentCaptor<DialogInterface.OnClickListener>()
@@ -105,4 +136,20 @@
 
             assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isTrue()
         }
+
+    private fun createAndSetDelegate(state: MediaProjectionState.Projecting) {
+        underTest =
+            EndShareToAppDialogDelegate(
+                kosmos.endMediaProjectionDialogHelper,
+                kosmos.mediaProjectionChipInteractor,
+                state,
+            )
+    }
+
+    companion object {
+        private const val HOST_PACKAGE = "fake.host.package"
+        private val ENTIRE_SCREEN = MediaProjectionState.Projecting.EntireScreen(HOST_PACKAGE)
+        private val SINGLE_TASK =
+            MediaProjectionState.Projecting.SingleTask(HOST_PACKAGE, createTask(taskId = 1))
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index 6712963..65bf0bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
@@ -39,8 +40,7 @@
 
 @SmallTest
 class OngoingActivityChipsViewModelTest : SysuiTestCase() {
-
-    private val kosmos = Kosmos()
+    private val kosmos = Kosmos().also { it.testCase = this }
     private val testScope = kosmos.testScope
 
     private val screenRecordState = kosmos.screenRecordRepository.screenRecordState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
index 057dcb2..6af14e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
 import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -390,7 +391,7 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.statusBarAppearance)
 
-            ongoingCallRepository.setHasOngoingCall(true)
+            ongoingCallRepository.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 34))
             onSystemBarAttributesChanged(
                 requestedVisibleTypes = WindowInsets.Type.navigationBars(),
             )
@@ -403,7 +404,7 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.statusBarAppearance)
 
-            ongoingCallRepository.setHasOngoingCall(true)
+            ongoingCallRepository.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 789))
             onSystemBarAttributesChanged(
                 requestedVisibleTypes = WindowInsets.Type.statusBars(),
                 appearance = APPEARANCE_OPAQUE_STATUS_BARS,
@@ -417,7 +418,7 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.statusBarAppearance)
 
-            ongoingCallRepository.setHasOngoingCall(false)
+            ongoingCallRepository.setOngoingCallState(OngoingCallModel.NoCall)
             onSystemBarAttributesChanged(
                 requestedVisibleTypes = WindowInsets.Type.navigationBars(),
                 appearance = APPEARANCE_OPAQUE_STATUS_BARS,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 4d6798b..feef943 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -32,16 +32,17 @@
 import android.widget.LinearLayout
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
 import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
+import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -60,13 +61,13 @@
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.ArgumentMatchers.nullable
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 private const val CALL_UID = 900
@@ -93,8 +94,8 @@
     private lateinit var controller: OngoingCallController
     private lateinit var notifCollectionListener: NotifCollectionListener
 
-    @Mock private lateinit var mockSwipeStatusBarAwayGestureHandler:
-        SwipeStatusBarAwayGestureHandler
+    @Mock
+    private lateinit var mockSwipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler
     @Mock private lateinit var mockOngoingCallListener: OngoingCallListener
     @Mock private lateinit var mockActivityStarter: ActivityStarter
     @Mock private lateinit var mockIActivityManager: IActivityManager
@@ -112,21 +113,22 @@
         MockitoAnnotations.initMocks(this)
         val notificationCollection = mock(CommonNotifCollection::class.java)
 
-        controller = OngoingCallController(
-            testScope.backgroundScope,
-            context,
-            ongoingCallRepository,
-            notificationCollection,
-            clock,
-            mockActivityStarter,
-            mainExecutor,
-            mockIActivityManager,
-            OngoingCallLogger(uiEventLoggerFake),
-            DumpManager(),
-            mockStatusBarWindowController,
-            mockSwipeStatusBarAwayGestureHandler,
-            statusBarModeRepository,
-        )
+        controller =
+            OngoingCallController(
+                testScope.backgroundScope,
+                context,
+                ongoingCallRepository,
+                notificationCollection,
+                clock,
+                mockActivityStarter,
+                mainExecutor,
+                mockIActivityManager,
+                OngoingCallLogger(uiEventLoggerFake),
+                DumpManager(),
+                mockStatusBarWindowController,
+                mockSwipeStatusBarAwayGestureHandler,
+                statusBarModeRepository,
+            )
         controller.start()
         controller.addCallback(mockOngoingCallListener)
         controller.setChipView(chipView)
@@ -136,7 +138,7 @@
         notifCollectionListener = collectionListenerCaptor.value!!
 
         `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
-                .thenReturn(PROC_STATE_INVISIBLE)
+            .thenReturn(PROC_STATE_INVISIBLE)
     }
 
     @After
@@ -146,10 +148,14 @@
 
     @Test
     fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() {
-        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+        val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
+        notification.modifyNotification(context).setWhen(567)
+        notifCollectionListener.onEntryUpdated(notification.build())
 
         verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
-        assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
+        val repoState = ongoingCallRepository.ongoingCallState.value
+        assertThat(repoState).isInstanceOf(OngoingCallModel.InCall::class.java)
+        assertThat((repoState as OngoingCallModel.InCall).startTimeMs).isEqualTo(567)
     }
 
     @Test
@@ -164,7 +170,8 @@
         notifCollectionListener.onEntryUpdated(createNotCallNotifEntry())
 
         verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
-        assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
+        assertThat(ongoingCallRepository.ongoingCallState.value)
+            .isInstanceOf(OngoingCallModel.NoCall::class.java)
     }
 
     @Test
@@ -172,25 +179,27 @@
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
         notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry())
 
-        verify(mockOngoingCallListener, times(2))
-                .onOngoingCallStateChanged(anyBoolean())
+        verify(mockOngoingCallListener, times(2)).onOngoingCallStateChanged(anyBoolean())
     }
 
     @Test
     fun onEntryUpdated_ongoingCallNotifThenScreeningCallNotif_repoUpdated() {
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-        assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
+        assertThat(ongoingCallRepository.ongoingCallState.value)
+            .isInstanceOf(OngoingCallModel.InCall::class.java)
 
         notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry())
 
-        assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
+        assertThat(ongoingCallRepository.ongoingCallState.value)
+            .isInstanceOf(OngoingCallModel.NoCall::class.java)
     }
 
     /** Regression test for b/191472854. */
     @Test
     fun onEntryUpdated_notifHasNullContentIntent_noCrash() {
         notifCollectionListener.onEntryUpdated(
-                createCallNotifEntry(ongoingCallStyle, nullContentIntent = true))
+            createCallNotifEntry(ongoingCallStyle, nullContentIntent = true)
+        )
     }
 
     /** Regression test for b/192379214. */
@@ -202,12 +211,12 @@
 
         notifCollectionListener.onEntryUpdated(notification.build())
         chipView.measure(
-                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
-                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
         )
 
         assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
-                .isEqualTo(0)
+            .isEqualTo(0)
     }
 
     @Test
@@ -218,12 +227,12 @@
 
         notifCollectionListener.onEntryUpdated(notification.build())
         chipView.measure(
-                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
-                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
         )
 
         assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
-                .isGreaterThan(0)
+            .isGreaterThan(0)
     }
 
     @Test
@@ -233,12 +242,12 @@
 
         notifCollectionListener.onEntryUpdated(notification.build())
         chipView.measure(
-                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
-                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
         )
 
         assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
-                .isGreaterThan(0)
+            .isGreaterThan(0)
     }
 
     /** Regression test for b/194731244. */
@@ -250,15 +259,14 @@
             notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
         }
 
-        verify(mockIActivityManager, times(1))
-            .registerUidObserver(any(), any(), any(), any())
+        verify(mockIActivityManager, times(1)).registerUidObserver(any(), any(), any(), any())
     }
 
     /** Regression test for b/216248574. */
     @Test
     fun entryUpdated_getUidProcessStateThrowsException_noCrash() {
         `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
-                .thenThrow(SecurityException())
+            .thenThrow(SecurityException())
 
         // No assert required, just check no crash
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
@@ -267,9 +275,15 @@
     /** Regression test for b/216248574. */
     @Test
     fun entryUpdated_registerUidObserverThrowsException_noCrash() {
-        `when`(mockIActivityManager.registerUidObserver(
-            any(), any(), any(), nullable(String::class.java)
-        )).thenThrow(SecurityException())
+        `when`(
+                mockIActivityManager.registerUidObserver(
+                    any(),
+                    any(),
+                    any(),
+                    nullable(String::class.java),
+                )
+            )
+            .thenThrow(SecurityException())
 
         // No assert required, just check no crash
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
@@ -281,9 +295,8 @@
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
 
         val packageNameCaptor = ArgumentCaptor.forClass(String::class.java)
-        verify(mockIActivityManager).registerUidObserver(
-            any(), any(), any(), packageNameCaptor.capture()
-        )
+        verify(mockIActivityManager)
+            .registerUidObserver(any(), any(), any(), packageNameCaptor.capture())
         assertThat(packageNameCaptor.value).isNotNull()
     }
 
@@ -313,11 +326,13 @@
     fun onEntryRemoved_callNotifAddedThenRemoved_repoUpdated() {
         val ongoingCallNotifEntry = createOngoingCallNotifEntry()
         notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
-        assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
+        assertThat(ongoingCallRepository.ongoingCallState.value)
+            .isInstanceOf(OngoingCallModel.InCall::class.java)
 
         notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
 
-        assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
+        assertThat(ongoingCallRepository.ongoingCallState.value)
+            .isInstanceOf(OngoingCallModel.NoCall::class.java)
     }
 
     @Test
@@ -360,7 +375,8 @@
 
         notifCollectionListener.onEntryRemoved(removedEntryBuilder.build(), REASON_USER_STOPPED)
 
-        assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse()
+        assertThat(ongoingCallRepository.ongoingCallState.value)
+            .isInstanceOf(OngoingCallModel.NoCall::class.java)
     }
 
     @Test
@@ -379,7 +395,8 @@
 
         notifCollectionListener.onEntryRemoved(createNotCallNotifEntry(), REASON_USER_STOPPED)
 
-        assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue()
+        assertThat(ongoingCallRepository.ongoingCallState.value)
+            .isInstanceOf(OngoingCallModel.InCall::class.java)
     }
 
     @Test
@@ -404,7 +421,7 @@
     @Test
     fun hasOngoingCall_ongoingCallNotifSentAndCallAppNotVisible_returnsTrue() {
         `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
-                .thenReturn(PROC_STATE_INVISIBLE)
+            .thenReturn(PROC_STATE_INVISIBLE)
 
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
 
@@ -414,7 +431,7 @@
     @Test
     fun hasOngoingCall_ongoingCallNotifSentButCallAppVisible_returnsFalse() {
         `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
-                .thenReturn(PROC_STATE_VISIBLE)
+            .thenReturn(PROC_STATE_VISIBLE)
 
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
 
@@ -472,10 +489,8 @@
 
         lateinit var newChipView: View
         TestableLooper.get(this).runWithLooper {
-            newChipView = LayoutInflater.from(mContext).inflate(
-                    R.layout.ongoing_activity_chip,
-                    null
-            )
+            newChipView =
+                LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
         }
 
         // Change the chip view associated with the controller.
@@ -488,13 +503,13 @@
     fun callProcessChangesToVisible_listenerNotified() {
         // Start the call while the process is invisible.
         `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
-                .thenReturn(PROC_STATE_INVISIBLE)
+            .thenReturn(PROC_STATE_INVISIBLE)
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
         reset(mockOngoingCallListener)
 
         val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java)
-        verify(mockIActivityManager).registerUidObserver(
-                captor.capture(), any(), any(), nullable(String::class.java))
+        verify(mockIActivityManager)
+            .registerUidObserver(captor.capture(), any(), any(), nullable(String::class.java))
         val uidObserver = captor.value
 
         // Update the process to visible.
@@ -509,13 +524,13 @@
     fun callProcessChangesToInvisible_listenerNotified() {
         // Start the call while the process is visible.
         `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
-                .thenReturn(PROC_STATE_VISIBLE)
+            .thenReturn(PROC_STATE_VISIBLE)
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
         reset(mockOngoingCallListener)
 
         val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java)
-        verify(mockIActivityManager).registerUidObserver(
-                captor.capture(), any(), any(), nullable(String::class.java))
+        verify(mockIActivityManager)
+            .registerUidObserver(captor.capture(), any(), any(), nullable(String::class.java))
         val uidObserver = captor.value
 
         // Update the process to invisible.
@@ -534,7 +549,7 @@
 
         assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
         assertThat(uiEventLoggerFake.eventId(0))
-                .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_CLICKED.id)
+            .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_CLICKED.id)
     }
 
     /** Regression test for b/212467440. */
@@ -556,8 +571,9 @@
 
         assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
         assertThat(uiEventLoggerFake.eventId(0))
-                .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_VISIBLE.id)
+            .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_VISIBLE.id)
     }
+
     // Other tests for notifyChipVisibilityChanged are in [OngoingCallLogger], since
     // [OngoingCallController.notifyChipVisibilityChanged] just delegates to that class.
 
@@ -621,8 +637,7 @@
         statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false
         testScope.runCurrent()
 
-        verify(mockSwipeStatusBarAwayGestureHandler)
-            .removeOnGestureDetectedCallback(anyString())
+        verify(mockSwipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(anyString())
     }
 
     @Test
@@ -635,8 +650,7 @@
 
         notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
 
-        verify(mockSwipeStatusBarAwayGestureHandler)
-            .removeOnGestureDetectedCallback(anyString())
+        verify(mockSwipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(anyString())
     }
 
     // TODO(b/195839150): Add test
@@ -675,5 +689,9 @@
 private val hangUpIntent = mock(PendingIntent::class.java)
 
 private val ongoingCallStyle = Notification.CallStyle.forOngoingCall(person, hangUpIntent)
-private val screeningCallStyle = Notification.CallStyle.forScreeningCall(
-        person, hangUpIntent, /* answerIntent= */ mock(PendingIntent::class.java))
\ No newline at end of file
+private val screeningCallStyle =
+    Notification.CallStyle.forScreeningCall(
+        person,
+        hangUpIntent,
+        /* answerIntent= */ mock(PendingIntent::class.java),
+    )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
index 56aa7d6..73a86a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 
@@ -27,12 +28,13 @@
 
     @Test
     fun hasOngoingCall_matchesSet() {
-        underTest.setHasOngoingCall(true)
+        val inCallModel = OngoingCallModel.InCall(startTimeMs = 654)
+        underTest.setOngoingCallState(inCallModel)
 
-        assertThat(underTest.hasOngoingCall.value).isTrue()
+        assertThat(underTest.ongoingCallState.value).isEqualTo(inCallModel)
 
-        underTest.setHasOngoingCall(false)
+        underTest.setOngoingCallState(OngoingCallModel.NoCall)
 
-        assertThat(underTest.hasOngoingCall.value).isFalse()
+        assertThat(underTest.ongoingCallState.value).isEqualTo(OngoingCallModel.NoCall)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
index 95b132d..3de50c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
@@ -19,6 +19,7 @@
 import android.os.PersistableBundle
 import android.telephony.CarrierConfigManager
 import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
+import android.telephony.CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL
 import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -53,16 +54,19 @@
     fun processNewConfig_updatesAllFlows() {
         assertThat(underTest.shouldInflateSignalStrength.value).isFalse()
         assertThat(underTest.showOperatorNameInStatusBar.value).isFalse()
+        assertThat(underTest.allowNetworkSliceIndicator.value).isTrue()
 
         underTest.processNewCarrierConfig(
             configWithOverrides(
                 KEY_INFLATE_SIGNAL_STRENGTH_BOOL to true,
                 KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL to true,
+                KEY_SHOW_5G_SLICE_ICON_BOOL to false,
             )
         )
 
         assertThat(underTest.shouldInflateSignalStrength.value).isTrue()
         assertThat(underTest.showOperatorNameInStatusBar.value).isTrue()
+        assertThat(underTest.allowNetworkSliceIndicator.value).isFalse()
     }
 
     @Test
@@ -79,12 +83,14 @@
                 configWithOverrides(
                     KEY_INFLATE_SIGNAL_STRENGTH_BOOL to true,
                     KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL to true,
+                    KEY_SHOW_5G_SLICE_ICON_BOOL to true,
                 )
             )
 
         assertThat(underTest.isUsingDefault).isTrue()
         assertThat(underTest.shouldInflateSignalStrength.value).isTrue()
         assertThat(underTest.showOperatorNameInStatusBar.value).isTrue()
+        assertThat(underTest.allowNetworkSliceIndicator.value).isTrue()
 
         // Process a new config with no keys
         underTest.processNewCarrierConfig(PersistableBundle())
@@ -92,6 +98,7 @@
         assertThat(underTest.isUsingDefault).isFalse()
         assertThat(underTest.shouldInflateSignalStrength.value).isFalse()
         assertThat(underTest.showOperatorNameInStatusBar.value).isFalse()
+        assertThat(underTest.allowNetworkSliceIndicator.value).isFalse()
     }
 
     companion object {
@@ -105,6 +112,7 @@
             PersistableBundle().also {
                 it.putBoolean(CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
                 it.putBoolean(CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL, false)
+                it.putBoolean(CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL, true)
             }
 
         /** Override the default config with the given (key, value) pair */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 3695d8c..6d8bf55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -25,6 +25,7 @@
 import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN
 import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN
 import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
+import android.telephony.CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL
 import android.telephony.NetworkRegistrationInfo
 import android.telephony.NetworkRegistrationInfo.DOMAIN_PS
 import android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_DENIED
@@ -1044,6 +1045,24 @@
         }
 
     @Test
+    fun allowNetworkSliceIndicator_exposesCarrierConfigValue() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.allowNetworkSliceIndicator)
+
+            systemUiCarrierConfig.processNewCarrierConfig(
+                configWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, true)
+            )
+
+            assertThat(latest).isTrue()
+
+            systemUiCarrierConfig.processNewCarrierConfig(
+                configWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, false)
+            )
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
     fun isAllowedDuringAirplaneMode_alwaysFalse() =
         testScope.runTest {
             val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index dfe8023..1488418 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -194,6 +194,50 @@
         }
 
     @Test
+    fun networkSlice_configOn_hasPrioritizedCaps_showsSlice() =
+        testScope.runTest {
+            connectionRepository.allowNetworkSliceIndicator.value = true
+            val latest by collectLastValue(underTest.showSliceAttribution)
+
+            connectionRepository.hasPrioritizedNetworkCapabilities.value = true
+
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun networkSlice_configOn_noPrioritizedCaps_noSlice() =
+        testScope.runTest {
+            connectionRepository.allowNetworkSliceIndicator.value = true
+            val latest by collectLastValue(underTest.showSliceAttribution)
+
+            connectionRepository.hasPrioritizedNetworkCapabilities.value = false
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun networkSlice_configOff_hasPrioritizedCaps_noSlice() =
+        testScope.runTest {
+            connectionRepository.allowNetworkSliceIndicator.value = false
+            val latest by collectLastValue(underTest.showSliceAttribution)
+
+            connectionRepository.hasPrioritizedNetworkCapabilities.value = true
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun networkSlice_configOff_noPrioritizedCaps_noSlice() =
+        testScope.runTest {
+            connectionRepository.allowNetworkSliceIndicator.value = false
+            val latest by collectLastValue(underTest.showSliceAttribution)
+
+            connectionRepository.hasPrioritizedNetworkCapabilities.value = false
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
     fun iconGroup_three_g() =
         testScope.runTest {
             connectionRepository.resolvedNetworkType.value =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index cdb2b88..b8299e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.assertLogsWtf
@@ -63,7 +64,10 @@
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
 class CollapsedStatusBarViewModelImplTest : SysuiTestCase() {
-    private val kosmos = Kosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
+    private val kosmos = Kosmos().also {
+        it.testCase = this
+        it.testDispatcher = UnconfinedTestDispatcher()
+    }
 
     private val testScope = kosmos.testScope
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
index e3c218d..3f38408 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.ui.viewmodel
 
 import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.util.communalColors
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.viewmodel.dreamingToGlanceableHubTransitionViewModel
@@ -37,6 +38,7 @@
             dreamToGlanceableHubTransitionViewModel = dreamingToGlanceableHubTransitionViewModel,
             glanceableHubToDreamTransitionViewModel = glanceableHubToDreamingTransitionViewModel,
             communalInteractor = communalInteractor,
+            communalSceneInteractor = communalSceneInteractor,
             keyguardTransitionInteractor = keyguardTransitionInteractor,
             communalColors = communalColors,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 22b8c8db..2d100f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -80,7 +80,7 @@
     override val isAodAvailable: StateFlow<Boolean> = _isAodAvailable
 
     private val _isDreaming = MutableStateFlow(false)
-    override val isDreaming: Flow<Boolean> = _isDreaming
+    override val isDreaming: MutableStateFlow<Boolean> = _isDreaming
 
     private val _isDreamingWithOverlay = MutableStateFlow(false)
     override val isDreamingWithOverlay: Flow<Boolean> = _isDreamingWithOverlay
@@ -204,7 +204,7 @@
         _isAodAvailable.value = value
     }
 
-    fun setDreaming(isDreaming: Boolean) {
+    override fun setDreaming(isDreaming: Boolean) {
         _isDreaming.value = isDreaming
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt
index 062b448..9d22811 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorKosmos.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
-import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
 import com.android.systemui.util.time.fakeSystemClock
 
 val Kosmos.mediaProjectionChipInteractor: MediaProjectionChipInteractor by
@@ -31,7 +31,7 @@
             mediaProjectionRepository = fakeMediaProjectionRepository,
             packageManager = packageManager,
             systemClock = fakeSystemClock,
-            dialogFactory = mockSystemUIDialogFactory,
+            endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
             dialogTransitionAnimator = mockDialogTransitionAnimator,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
new file mode 100644
index 0000000..4f82662
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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.chips.mediaprojection.ui.view
+
+import android.content.applicationContext
+import android.content.packageManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+
+val Kosmos.endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper by
+    Kosmos.Fixture {
+        EndMediaProjectionDialogHelper(
+            dialogFactory = mockSystemUIDialogFactory,
+            packageManager = packageManager,
+            context = applicationContext,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index eb2d6c0..c3c3cce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -32,6 +32,7 @@
 ) : MobileConnectionRepository {
     override val carrierId = MutableStateFlow(UNKNOWN_CARRIER_ID)
     override val inflateSignalStrength: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val allowNetworkSliceIndicator: MutableStateFlow<Boolean> = MutableStateFlow(true)
     override val isEmergencyOnly = MutableStateFlow(false)
     override val isRoaming = MutableStateFlow(false)
     override val operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
diff --git a/proto/src/ondeviceintelligence/OWNERS b/proto/src/ondeviceintelligence/OWNERS
new file mode 100644
index 0000000..09774f7
--- /dev/null
+++ b/proto/src/ondeviceintelligence/OWNERS
@@ -0,0 +1 @@
+file:/core/java/android/app/ondeviceintelligence/OWNERS
diff --git a/proto/src/ondeviceintelligence/inference_info.proto b/proto/src/ondeviceintelligence/inference_info.proto
new file mode 100644
index 0000000..a6f4f4f
--- /dev/null
+++ b/proto/src/ondeviceintelligence/inference_info.proto
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2024 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.
+ */
+
+syntax = "proto2";
+
+package android.ondeviceintelligence;
+
+option java_package = "com.android.server.ondeviceintelligence";
+option java_multiple_files = true;
+
+
+message InferenceInfo {
+  // Uid for the caller app.
+  optional int32 uid = 1;
+  // Inference start time(milliseconds from the epoch time).
+  optional int64 start_time_ms = 2;
+  // Inference end time(milliseconds from the epoch time).
+  optional int64 end_time_ms = 3;
+  // Suspended time in milliseconds.
+  optional int64 suspended_time_ms = 4;
+}
\ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 9ad15b2..2bf319e 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -515,7 +515,10 @@
         }
 
         final boolean finished = saveResult.isRemoveSession();
-        if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
+        if (sVerbose) {
+            Slog.v(TAG, "finishSessionLocked(): session finished? " + finished
+                    + ", showing save UI? " + saveResult.isLogSaveShown());
+        }
 
         if (finished) {
             session.removeFromServiceLocked();
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 435a671..676abd1 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -641,6 +641,15 @@
     }
 
     /**
+     * Set how many views are filtered from fill because they are not in current session
+     */
+    public void maybeSetFilteredFillableViewsCount(int filteredViewsCount) {
+        mEventInternal.ifPresent(event -> {
+            event.mFilteredFillabaleViewCount = filteredViewsCount;
+        });
+    }
+
+    /**
      * Set views_filled_failure_count using failure count as long as mEventInternal
      * presents.
      */
@@ -735,6 +744,7 @@
                     + " mAppPackageUid=" + mCallingAppUid
                     + " mIsCredentialRequest=" + event.mIsCredentialRequest
                     + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential
+                    + " mFilteredFillabaleViewCount=" + event.mFilteredFillabaleViewCount
                     + " mViewFillableTotalCount=" + event.mViewFillableTotalCount
                     + " mViewFillFailureCount=" + event.mViewFillFailureCount
                     + " mFocusedId=" + event.mFocusedId
@@ -791,6 +801,7 @@
                 mCallingAppUid,
                 event.mIsCredentialRequest,
                 event.mWebviewRequestedCredential,
+                event.mFilteredFillabaleViewCount,
                 event.mViewFillableTotalCount,
                 event.mViewFillFailureCount,
                 event.mFocusedId,
@@ -839,6 +850,7 @@
         int mFieldClassificationRequestId = DEFAULT_VALUE_INT;
         boolean mIsCredentialRequest = false;
         boolean mWebviewRequestedCredential = false;
+        int mFilteredFillabaleViewCount = DEFAULT_VALUE_INT;
         int mViewFillableTotalCount = DEFAULT_VALUE_INT;
         int mViewFillFailureCount = DEFAULT_VALUE_INT;
         int mFocusedId = DEFAULT_VALUE_INT;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index ecb0ad8..b22f999 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -5441,9 +5441,10 @@
 
         try {
             if (sVerbose) {
-                Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds
-                        + " triggerId: " + saveTriggerId + " saveOnFinish:" + saveOnFinish
-                        + " flags: " + flags + " hasSaveInfo: " + (saveInfo != null));
+                Slog.v(TAG, "updateTrackedIdsLocked(): trackedViews: " + trackedViews
+                        + " fillableIds: " + fillableIds + " triggerId: " + saveTriggerId
+                        + " saveOnFinish:" + saveOnFinish + " flags: " + flags
+                        + " hasSaveInfo: " + (saveInfo != null));
             }
             mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible,
                     saveOnFinish, toArray(fillableIds), saveTriggerId);
@@ -6664,6 +6665,8 @@
                 boolean waitingDatasetAuth = false;
                 boolean hideHighlight = (entryCount == 1
                         && dataset.getFieldIds().get(0).equals(mCurrentViewId));
+                // Count how many views are filtered because they are not in current session
+                int numOfViewsFiltered = 0;
                 for (int i = 0; i < entryCount; i++) {
                     if (dataset.getFieldValues().get(i) == null) {
                         continue;
@@ -6676,6 +6679,7 @@
                             Slog.v(TAG, "Skipping filling view: " +
                                     viewId + " as it isn't part of the current session: " + id);
                         }
+                        numOfViewsFiltered += 1;
                         continue;
                     }
                     ids.add(viewId);
@@ -6689,6 +6693,8 @@
                         viewState.resetState(ViewState.STATE_WAITING_DATASET_AUTH);
                     }
                 }
+                mPresentationStatsEventLogger.maybeSetFilteredFillableViewsCount(
+                        numOfViewsFiltered);
                 if (!ids.isEmpty()) {
                     if (waitingDatasetAuth) {
                         mUi.hideFillUi(this);
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 966478e..eb03709 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -663,6 +663,7 @@
                      PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
                      PackageHealthObserverImpact.USER_IMPACT_LEVEL_20,
                      PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_40,
                      PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
                      PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
                      PackageHealthObserverImpact.USER_IMPACT_LEVEL_71,
@@ -678,6 +679,7 @@
         /* Actions having medium user impact, user of a device will likely notice. */
         int USER_IMPACT_LEVEL_20 = 20;
         int USER_IMPACT_LEVEL_30 = 30;
+        int USER_IMPACT_LEVEL_40 = 40;
         int USER_IMPACT_LEVEL_50 = 50;
         int USER_IMPACT_LEVEL_70 = 70;
         /* Action has high user impact, a last resort, user of a device will be very frustrated. */
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 37c2d26..189b2495 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -673,7 +673,7 @@
                 case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
                     return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
                 case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
-                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_20;
+                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_40;
                 case RESCUE_LEVEL_WARM_REBOOT:
                     return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
                 case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d750065..5ffab55 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -14642,7 +14642,7 @@
             final StringBuilder sb = new StringBuilder("registerReceiver: ");
             sb.append(Binder.getCallingUid()); sb.append('/');
             sb.append(receiverId == null ? "null" : receiverId); sb.append('/');
-            final int actionsCount = filter.countActions();
+            final int actionsCount = filter.safeCountActions();
             if (actionsCount > 0) {
                 for (int i = 0; i < actionsCount; ++i) {
                     sb.append(filter.getAction(i));
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 039e7f4..bce1830 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -275,7 +275,7 @@
      * Handle BluetoothHeadset intents where the action is one of
      *   {@link BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED} or
      *   {@link BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED}.
-     * @param intent
+     * @param intent the Intent received from BT service
      */
     private void onReceiveBtEvent(@NonNull Intent intent) {
         mBtHelper.onReceiveBtEvent(intent);
@@ -296,7 +296,7 @@
 
     /**
      * Turns speakerphone on/off
-     * @param on
+     * @param on true to enable speakerphone
      * @param eventSource for logging purposes
      */
     /*package*/ void setSpeakerphoneOn(
@@ -310,21 +310,21 @@
                 on, BtHelper.SCO_MODE_UNDEFINED, eventSource, isPrivileged));
     }
 
+    private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000;
+
+    /** synchronization for setCommunicationDevice() and getCommunicationDevice */
+    private final Object mCommunicationDeviceLock = new Object();
+    @GuardedBy("mCommunicationDeviceLock")
+    private int mCommunicationDeviceUpdateCount = 0;
+
     /**
      * Select device for use for communication use cases.
      * @param cb Client binder for death detection
      * @param uid Client uid
      * @param device Device selected or null to unselect.
      * @param eventSource for logging purposes
+     * @return false if there is no device and no communication client
      */
-
-    private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000;
-
-    /** synchronization for setCommunicationDevice() and getCommunicationDevice */
-    private Object mCommunicationDeviceLock = new Object();
-    @GuardedBy("mCommunicationDeviceLock")
-    private int mCommunicationDeviceUpdateCount = 0;
-
     /*package*/ boolean setCommunicationDevice(IBinder cb, int uid, AudioDeviceInfo device,
                                                boolean isPrivileged, String eventSource) {
 
@@ -355,7 +355,6 @@
      * Sets or resets the communication device for matching client. If no client matches and the
      * request is to reset for a given device (deviceInfo.mOn == false), the method is a noop.
      * @param deviceInfo information on the device and requester {@link #CommunicationDeviceInfo}
-     * @return true if the communication device is set or reset
      */
     @GuardedBy("mDeviceStateLock")
     /*package*/ void onSetCommunicationDeviceForClient(CommunicationDeviceInfo deviceInfo) {
@@ -742,15 +741,6 @@
     }
 
     /**
-     * Helper method on top of isDeviceRequestedForCommunication() indicating if
-     * speakerphone ON is currently requested or not.
-     * @return true if speakerphone ON requested, false otherwise.
-     */
-    private boolean isSpeakerphoneRequested() {
-        return isDeviceRequestedForCommunication(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
-    }
-
-    /**
      * Indicates if preferred route selection for communication is speakerphone.
      * @return true if speakerphone is active, false otherwise.
      */
@@ -929,6 +919,12 @@
         }
 
         @Override
+        public int hashCode() {
+            // only hashing on the fields used in equals()
+            return Objects.hash(mProfile, mDevice);
+        }
+
+        @Override
         public String toString() {
             return "BtDeviceInfo: device=" + mDevice.toString()
                             + " state=" + mState
@@ -990,7 +986,7 @@
     /**
      * will block on mDeviceStateLock, which is held during an A2DP (dis) connection
      * not just a simple message post
-     * @param info struct with the (dis)connection information
+     * @param data struct with the (dis)connection information
      */
     /*package*/ void queueOnBluetoothActiveDeviceChanged(@NonNull BtDeviceChangedData data) {
         if (data.mPreviousDevice != null
@@ -1379,6 +1375,7 @@
                 mCommDevDispatchers.getBroadcastItem(i)
                         .dispatchCommunicationDeviceChanged(portId);
             } catch (RemoteException e) {
+                Log.e(TAG, "dispatchCommunicationDevice error", e);
             }
         }
         mCommDevDispatchers.finishBroadcast();
@@ -1581,6 +1578,12 @@
         }
 
         @Override
+        public int hashCode() {
+            // only hashing on the fields used in equals()
+            return Objects.hash(mCb.hashCode(), mUid);
+        }
+
+        @Override
         public String toString() {
             return "CommunicationDeviceInfo mCb=" + mCb.toString()
                     + " mUid=" + mUid
@@ -1600,9 +1603,9 @@
     //@GuardedBy("mConnectedDevices")
     /*package*/ void setBluetoothA2dpOnInt(boolean on, boolean fromA2dp, String source) {
         // for logging only
-        final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on)
-                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
-                .append(Binder.getCallingPid()).append(" src:").append(source).toString();
+        final String eventSource = "setBluetoothA2dpOn(" + on
+                + ") from u/pid:" + Binder.getCallingUid() + "/"
+                + Binder.getCallingPid() + " src:" + source;
 
         mBluetoothA2dpEnabled.set(on);
         onSetForceUse(
@@ -2203,10 +2206,6 @@
         sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, delay);
     }
 
-    private void sendIMsg(int msg, int existingMsgPolicy, int arg, int delay) {
-        sendIILMsg(msg, existingMsgPolicy, arg, 0, null, delay);
-    }
-
     private void sendMsgNoDelay(int msg, int existingMsgPolicy) {
         sendIILMsg(msg, existingMsgPolicy, 0, 0, null, 0);
     }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 287c92f..ba7aee0 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -449,7 +449,7 @@
         @Override
         public DeviceInfo put(String key, DeviceInfo value) {
             final DeviceInfo result = super.put(key, value);
-            record("put", true /* connected */, key, value);
+            record("put", true /* connected */, value);
             return result;
         }
 
@@ -457,7 +457,7 @@
         public DeviceInfo putIfAbsent(String key, DeviceInfo value) {
             final DeviceInfo result = super.putIfAbsent(key, value);
             if (result == null) {
-                record("putIfAbsent", true /* connected */, key, value);
+                record("putIfAbsent", true /* connected */, value);
             }
             return result;
         }
@@ -466,7 +466,7 @@
         public DeviceInfo remove(Object key) {
             final DeviceInfo result = super.remove(key);
             if (result != null) {
-                record("remove", false /* connected */, (String) key, result);
+                record("remove", false /* connected */, result);
             }
             return result;
         }
@@ -475,7 +475,7 @@
         public boolean remove(Object key, Object value) {
             final boolean result = super.remove(key, value);
             if (result) {
-                record("remove", false /* connected */, (String) key, (DeviceInfo) value);
+                record("remove", false /* connected */, (DeviceInfo) value);
             }
             return result;
         }
@@ -489,7 +489,7 @@
         // putAll
         // replace
         // replaceAll
-        private void record(String event, boolean connected, String key, DeviceInfo value) {
+        private void record(String event, boolean connected, DeviceInfo value) {
             // DeviceInfo - int mDeviceType;
             // DeviceInfo - int mDeviceCodecFormat;
             new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
@@ -662,7 +662,7 @@
     /**
      * A class just for packaging up a set of connection parameters.
      */
-    /*package*/ class WiredDeviceConnectionState {
+    /*package*/ static class WiredDeviceConnectionState {
         public final AudioDeviceAttributes mAttributes;
         public final @AudioService.ConnectionState int mState;
         public final String mCaller;
@@ -1054,7 +1054,9 @@
                 IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n);
                 try {
                     obs.dispatchAudioRoutesChanged(routes);
-                } catch (RemoteException e) { }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "onReportNewRoutes", e);
+                }
             }
         }
         mRoutesObservers.finishBroadcast();
@@ -1835,7 +1837,8 @@
                     .set(MediaMetrics.Property.EVENT, "disconnectHearingAid")
                     .record();
             if (toRemove.size() > 0) {
-                final int delay = checkSendBecomingNoisyIntentInt(DEVICE_OUT_HEARING_AID,
+                /*final int delay = */
+                checkSendBecomingNoisyIntentInt(DEVICE_OUT_HEARING_AID,
                         AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
                 toRemove.stream().forEach(deviceAddress ->
                         // TODO delay not used?
@@ -2697,10 +2700,6 @@
     private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
     private static final String CONNECT_INTENT_KEY_STATE = "state";
     private static final String CONNECT_INTENT_KEY_ADDRESS = "address";
-    private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
-    private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
-    private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
-    private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
 
     private void sendDeviceConnectionIntent(int device, int state, String address,
                                             String deviceName) {
@@ -2863,6 +2862,7 @@
                 mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged(
                         strategy, devices);
             } catch (RemoteException e) {
+                Log.e(TAG, "dispatchPreferredDevice ", e);
             }
         }
         mPrefDevDispatchers.finishBroadcast();
@@ -2879,6 +2879,7 @@
                 mNonDefDevDispatchers.getBroadcastItem(i).dispatchNonDefDevicesChanged(
                         strategy, devices);
             } catch (RemoteException e) {
+                Log.e(TAG, "dispatchNonDefaultDevice ", e);
             }
         }
         mNonDefDevDispatchers.finishBroadcast();
@@ -2895,6 +2896,7 @@
                 mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged(
                         capturePreset, role, devices);
             } catch (RemoteException e) {
+                Log.e(TAG, "dispatchDevicesRoleForCapturePreset ", e);
             }
         }
         mDevRoleCapturePresetDispatchers.finishBroadcast();
@@ -2960,7 +2962,7 @@
 
     /**
      * Check if device is in the list of connected devices
-     * @param device
+     * @param device the device to query
      * @return true if connected
      */
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5dd1480..684cb24 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -13911,9 +13911,8 @@
         final int stream = AudioAttributes.toLegacyStreamType(aa);
         final boolean mutingFromVolume = getStreamVolume(stream) == 0;
         if (mutingFromVolume) {
-            if (DEBUG_VOL) {
-                Slog.d(TAG, "notification should not play due to muted stream " + stream);
-            }
+            Slog.i(TAG, "shouldNotificationSoundPlay false: muted stream:" + stream
+                    + " attr:" + aa);
             return false;
         }
 
@@ -13926,10 +13925,8 @@
         // is the owner of GAIN_TRANSIENT_EXCLUSIVE focus also recording?
         final boolean mutingFromFocusAndRecording = mRecordMonitor.isRecordingActiveForUid(uid);
         if (mutingFromFocusAndRecording) {
-            if (DEBUG_VOL) {
-                Slog.d(TAG, "notification should not play due to exclusive focus owner recording "
-                        + " uid:" + uid);
-            }
+            Slog.i(TAG, "shouldNotificationSoundPlay false: exclusive focus owner recording "
+                        + " uid:" + uid + " attr:" + aa);
             return false;
         }
         return true;
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 1949e6f..7b5cff7 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -54,7 +54,6 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.server.EventLogTags;
 import com.android.server.display.brightness.BrightnessEvent;
-import com.android.server.display.brightness.clamper.BrightnessClamperController;
 import com.android.server.display.config.HysteresisLevels;
 import com.android.server.display.feature.DisplayManagerFlags;
 
@@ -256,7 +255,6 @@
 
     // Controls Brightness range (including High Brightness Mode).
     private final BrightnessRangeController mBrightnessRangeController;
-    private final BrightnessClamperController mBrightnessClamperController;
 
     // Throttles (caps) maximum allowed brightness
     private final BrightnessThrottler mBrightnessThrottler;
@@ -295,7 +293,6 @@
             BrightnessRangeController brightnessModeController,
             BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
             int ambientLightHorizonLong, float userLux, float userNits,
-            BrightnessClamperController brightnessClamperController,
             DisplayManagerFlags displayManagerFlags) {
         this(new Injector(), callbacks, looper, sensorManager, lightSensor,
                 brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin, brightnessMax,
@@ -306,7 +303,7 @@
                 screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
                 screenBrightnessThresholdsIdle, context, brightnessModeController,
                 brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
-                userNits, brightnessClamperController, displayManagerFlags
+                userNits, displayManagerFlags
         );
     }
 
@@ -325,7 +322,6 @@
             BrightnessRangeController brightnessRangeController,
             BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
             int ambientLightHorizonLong, float userLux, float userNits,
-            BrightnessClamperController brightnessClamperController,
             DisplayManagerFlags displayManagerFlags) {
         mInjector = injector;
         mClock = injector.createClock(displayManagerFlags.offloadControlsDozeAutoBrightness());
@@ -370,7 +366,6 @@
         mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
         mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
         mBrightnessRangeController = brightnessRangeController;
-        mBrightnessClamperController = brightnessClamperController;
         mBrightnessThrottler = brightnessThrottler;
         mBrightnessMappingStrategyMap = brightnessMappingStrategyMap;
         mDisplayManagerFlags = displayManagerFlags;
@@ -771,7 +766,6 @@
                     mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
         }
         mBrightnessRangeController.onAmbientLuxChange(mAmbientLux);
-        mBrightnessClamperController.onAmbientLuxChange(mAmbientLux);
 
         // If the short term model was invalidated and the change is drastic enough, reset it.
         mShortTermModel.maybeReset(mAmbientLux);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 195a516..65a729a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -587,7 +587,7 @@
                         mUniqueDisplayId,
                         mThermalBrightnessThrottlingDataId,
                         logicalDisplay.getPowerThrottlingDataIdLocked(),
-                        mDisplayDeviceConfig), mContext, flags);
+                        mDisplayDeviceConfig), mContext, flags, mSensorManager);
         // Seed the cached brightness
         saveBrightnessInfo(getScreenBrightnessSetting());
         mAutomaticBrightnessStrategy =
@@ -1422,7 +1422,6 @@
                         : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
 
         mBrightnessRangeController.setAutoBrightnessEnabled(autoBrightnessState);
-        mBrightnessClamperController.setAutoBrightnessState(autoBrightnessState);
 
         boolean updateScreenBrightnessSetting =
                 displayBrightnessState.shouldUpdateScreenBrightnessSetting();
@@ -1549,7 +1548,7 @@
         // we broadcast this change through setting.
         final float unthrottledBrightnessState = brightnessState;
         DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest,
-                brightnessState, slowChange);
+                brightnessState, slowChange, /* displayState= */ state);
 
         brightnessState = clampedState.getBrightness();
         slowChange = clampedState.isSlowChange();
@@ -2478,7 +2477,6 @@
     public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
             boolean slowChange) {
         mBrightnessRangeController.onAmbientLuxChange(ambientLux);
-        mBrightnessClamperController.onAmbientLuxChange(ambientLux);
         if (nits == BrightnessMappingStrategy.INVALID_NITS) {
             mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness, slowChange);
         } else {
@@ -3194,7 +3192,7 @@
                     screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
                     screenBrightnessThresholdsIdle, context, brightnessModeController,
                     brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
-                    userNits, brightnessClamperController, displayManagerFlags);
+                    userNits, displayManagerFlags);
         }
 
         BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
@@ -3243,10 +3241,10 @@
         BrightnessClamperController getBrightnessClamperController(Handler handler,
                 BrightnessClamperController.ClamperChangeListener clamperChangeListener,
                 BrightnessClamperController.DisplayDeviceData data, Context context,
-                DisplayManagerFlags flags) {
+                DisplayManagerFlags flags, SensorManager sensorManager) {
 
             return new BrightnessClamperController(handler, clamperChangeListener, data, context,
-                    flags);
+                    flags, sensorManager);
         }
 
         DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 11ef577..101ad30 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -16,21 +16,30 @@
 
 package com.android.server.display.brightness.clamper;
 
+import static android.view.Display.STATE_ON;
+
 import static com.android.server.display.brightness.clamper.BrightnessClamper.Type;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
 import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.PowerManager;
+import android.os.SystemClock;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfigInterface;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.DisplayDeviceConfig;
@@ -41,20 +50,30 @@
 import com.android.server.display.config.SensorData;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.utils.AmbientFilter;
+import com.android.server.display.utils.AmbientFilterFactory;
+import com.android.server.display.utils.DebugUtils;
+import com.android.server.display.utils.SensorUtils;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Clampers controller, all in DisplayControllerHandler
  */
 public class BrightnessClamperController {
     private static final String TAG = "BrightnessClamperController";
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.BrightnessClamperController DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+    public static final float INVALID_LUX = -1f;
 
     private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
     private final Handler mHandler;
+    private final SensorManager mSensorManager;
     private final ClamperChangeListener mClamperChangeListenerExternal;
     private final Executor mExecutor;
     private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers;
@@ -66,24 +85,55 @@
     private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
     @Nullable
     private Type mClamperType = null;
-    private int mAutoBrightnessState = -1;
+    private final SensorEventListener mLightSensorListener;
+    private Sensor mRegisteredLightSensor = null;
+    private Sensor mLightSensor;
+    private String mLightSensorType;
+    private String mLightSensorName;
+    private AmbientFilter mAmbientFilter;
+    private final DisplayDeviceConfig mDisplayDeviceConfig;
+    private final Resources mResources;
+    private final int mLightSensorRate;
 
+    private final Injector mInjector;
     private boolean mClamperApplied = false;
 
     public BrightnessClamperController(Handler handler,
             ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
-            DisplayManagerFlags flags) {
-        this(new Injector(), handler, clamperChangeListener, data, context, flags);
+            DisplayManagerFlags flags, SensorManager sensorManager) {
+        this(null, handler, clamperChangeListener, data, context, flags, sensorManager);
     }
 
     @VisibleForTesting
     BrightnessClamperController(Injector injector, Handler handler,
             ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
-            DisplayManagerFlags flags) {
-        mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+            DisplayManagerFlags flags, SensorManager sensorManager) {
+        mInjector = injector == null ? new Injector() : injector;
+        mDeviceConfigParameterProvider = mInjector.getDeviceConfigParameterProvider();
         mHandler = handler;
+        mSensorManager = sensorManager;
+        mDisplayDeviceConfig = data.mDisplayDeviceConfig;
+        mLightSensorListener = new SensorEventListener() {
+            @Override
+            public void onSensorChanged(SensorEvent event) {
+                long now = SystemClock.elapsedRealtime();
+                mAmbientFilter.addValue(TimeUnit.NANOSECONDS.toMillis(event.timestamp),
+                        event.values[0]);
+                final float lux = mAmbientFilter.getEstimate(now);
+                mModifiers.forEach(mModifier -> mModifier.setAmbientLux(lux));
+            }
+
+            @Override
+            public void onAccuracyChanged(Sensor sensor, int accuracy) {
+                // unused
+            }
+        };
+
         mClamperChangeListenerExternal = clamperChangeListener;
         mExecutor = new HandlerExecutor(handler);
+        mResources = context.getResources();
+        mLightSensorRate = context.getResources().getInteger(
+                R.integer.config_autoBrightnessLightSensorRate);
 
         Runnable clamperChangeRunnableInternal = this::recalculateBrightnessCap;
 
@@ -93,10 +143,10 @@
             }
         };
 
-        mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags,
+        mClampers = mInjector.getClampers(handler, clamperChangeListenerInternal, data, flags,
                 context);
-        mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListener,
-                data.mDisplayDeviceConfig);
+        mModifiers = mInjector.getModifiers(flags, context, handler, clamperChangeListener,
+                data.mDisplayDeviceConfig, mSensorManager);
         mOnPropertiesChangedListener =
                 properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
         start();
@@ -114,7 +164,7 @@
      * Called in DisplayControllerHandler
      */
     public DisplayBrightnessState clamp(DisplayManagerInternal.DisplayPowerRequest request,
-            float brightnessValue, boolean slowChange) {
+            float brightnessValue, boolean slowChange, int displayState) {
         float cappedBrightness = Math.min(brightnessValue, mBrightnessCap);
 
         DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
@@ -133,6 +183,12 @@
             mClamperApplied = false;
         }
 
+        if (displayState != STATE_ON) {
+            unregisterSensorListener();
+        } else {
+            maybeRegisterLightSensor();
+        }
+
         for (int i = 0; i < mModifiers.size(); i++) {
             mModifiers.get(i).apply(request, builder);
         }
@@ -175,6 +231,8 @@
         writer.println("  mBrightnessCap: " + mBrightnessCap);
         writer.println("  mClamperType: " + mClamperType);
         writer.println("  mClamperApplied: " + mClamperApplied);
+        writer.println("  mLightSensor=" + mLightSensor);
+        writer.println("  mRegisteredLightSensor=" + mRegisteredLightSensor);
         IndentingPrintWriter ipw = new IndentingPrintWriter(writer, "    ");
         mClampers.forEach(clamper -> clamper.dump(ipw));
         mModifiers.forEach(modifier -> modifier.dump(ipw));
@@ -191,26 +249,6 @@
         mModifiers.forEach(BrightnessStateModifier::stop);
     }
 
-    /**
-     * Notifies modifiers that ambient lux has changed.
-     * @param ambientLux current lux, debounced
-     */
-    public void onAmbientLuxChange(float ambientLux) {
-        mModifiers.forEach(modifier -> modifier.onAmbientLuxChange(ambientLux));
-    }
-
-    /**
-     * Sets the autobrightness state for clampers that need to be aware of the state.
-     * @param state autobrightness state
-     */
-    public void setAutoBrightnessState(int state) {
-        if (state == mAutoBrightnessState) {
-            return;
-        }
-        mModifiers.forEach(modifier -> modifier.setAutoBrightnessState(state));
-        mAutoBrightnessState = state;
-        recalculateBrightnessCap();
-    }
 
     // Called in DisplayControllerHandler
     private void recalculateBrightnessCap() {
@@ -243,6 +281,10 @@
         if (!mClampers.isEmpty()) {
             mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
                     mExecutor, mOnPropertiesChangedListener);
+            reloadLightSensorData(mDisplayDeviceConfig);
+            mLightSensor = mInjector.getLightSensor(
+                    mSensorManager, mLightSensorType, mLightSensorName);
+            maybeRegisterLightSensor();
         }
     }
 
@@ -281,7 +323,7 @@
 
         List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
                 Handler handler, ClamperChangeListener listener,
-                DisplayDeviceConfig displayDeviceConfig) {
+                DisplayDeviceConfig displayDeviceConfig, SensorManager sensorManager) {
             List<BrightnessStateModifier> modifiers = new ArrayList<>();
             modifiers.add(new DisplayDimModifier(context));
             modifiers.add(new BrightnessLowPowerModeModifier());
@@ -292,13 +334,19 @@
             }
             return modifiers;
         }
+
+        Sensor getLightSensor(SensorManager sensorManager, String type, String name) {
+            return SensorUtils.findSensor(sensorManager, type,
+                    name, Sensor.TYPE_LIGHT);
+        }
+
     }
 
     /**
      * Config Data for clampers
      */
     public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData,
-                BrightnessPowerClamper.PowerData,
+            BrightnessPowerClamper.PowerData,
             BrightnessWearBedtimeModeClamper.WearBedtimeModeData {
         @NonNull
         private final String mUniqueDisplayId;
@@ -368,4 +416,51 @@
             return mDisplayDeviceConfig.getTempSensor();
         }
     }
+
+    private void maybeRegisterLightSensor() {
+        if (mModifiers.stream().noneMatch(BrightnessStateModifier::shouldListenToLightSensor)) {
+            return;
+        }
+
+        if (mRegisteredLightSensor == mLightSensor) {
+            return;
+        }
+
+        if (mRegisteredLightSensor != null) {
+            unregisterSensorListener();
+        }
+
+        mAmbientFilter = AmbientFilterFactory.createBrightnessFilter(TAG, mResources);
+        mSensorManager.registerListener(mLightSensorListener,
+                mLightSensor, mLightSensorRate * 1000, mHandler);
+        mRegisteredLightSensor = mLightSensor;
+
+        if (DEBUG) {
+            Slog.d(TAG, "maybeRegisterLightSensor");
+        }
+    }
+
+    private void unregisterSensorListener() {
+        mSensorManager.unregisterListener(mLightSensorListener);
+        mRegisteredLightSensor = null;
+        mModifiers.forEach(mModifier -> mModifier.setAmbientLux(INVALID_LUX)); // set lux to invalid
+        if (DEBUG) {
+            Slog.d(TAG, "unregisterSensorListener");
+        }
+    }
+
+    private void reloadLightSensorData(DisplayDeviceConfig displayDeviceConfig) {
+        // The displayDeviceConfig (ddc) contains display specific preferences. When loaded,
+        // it naturally falls back to the global config.xml.
+        if (displayDeviceConfig != null
+                && displayDeviceConfig.getAmbientLightSensor() != null) {
+            // This covers both the ddc and the config.xml fallback
+            mLightSensorType = displayDeviceConfig.getAmbientLightSensor().type;
+            mLightSensorName = displayDeviceConfig.getAmbientLightSensor().name;
+        } else if (mLightSensorName == null && mLightSensorType == null) {
+            mLightSensorType = mResources.getString(
+                    com.android.internal.R.string.config_displayLightSensorType);
+            mLightSensorName = "";
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
index 7ba4a4d..951980a 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
@@ -16,7 +16,6 @@
 
 package com.android.server.display.brightness.clamper;
 
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
 
 import android.content.ContentResolver;
 import android.content.Context;
@@ -30,6 +29,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.BrightnessMappingStrategy;
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.DisplayDeviceConfig;
 import com.android.server.display.brightness.BrightnessReason;
@@ -56,7 +56,6 @@
     private float mBrightnessLowerBound;
     private float mMinNitsAllowed;
     private boolean mIsActive;
-    private boolean mAutoBrightnessEnabled;
     private float mAmbientLux;
     private final DisplayDeviceConfig mDisplayDeviceConfig;
 
@@ -87,15 +86,15 @@
                 mContentResolver, Settings.Secure.EVEN_DIMMER_MIN_NITS,
                 /* def= */ MIN_NITS_DEFAULT, userId);
 
-        boolean isActive = isSettingEnabled() && mAutoBrightnessEnabled;
-
-        float luxBasedNitsLowerBound = mDisplayDeviceConfig.getMinNitsFromLux(mAmbientLux);
+        boolean isActive = isSettingEnabled()
+                && mAmbientLux != BrightnessMappingStrategy.INVALID_LUX;
 
         final int reason;
         float minNitsAllowed = -1f; // undefined, if setting is off.
         final float minBrightnessAllowed;
 
         if (isActive) {
+            float luxBasedNitsLowerBound = mDisplayDeviceConfig.getMinNitsFromLux(mAmbientLux);
             minNitsAllowed = Math.max(settingNitsLowerBound,
                     luxBasedNitsLowerBound);
             minBrightnessAllowed = getBrightnessFromNits(minNitsAllowed);
@@ -127,6 +126,12 @@
     }
 
     @VisibleForTesting
+    public void setAmbientLux(float lux) {
+        mAmbientLux = lux;
+        recalculateLowerBound();
+    }
+
+    @VisibleForTesting
     public boolean isActive() {
         return mIsActive;
     }
@@ -164,10 +169,10 @@
     @Override
     public void apply(DisplayManagerInternal.DisplayPowerRequest request,
             DisplayBrightnessState.Builder stateBuilder) {
+
         stateBuilder.setMinBrightness(mBrightnessLowerBound);
         float boundedBrightness = Math.max(mBrightnessLowerBound, stateBuilder.getBrightness());
         stateBuilder.setBrightness(boundedBrightness);
-
         if (BrightnessSynchronizer.floatEquals(stateBuilder.getBrightness(),
                 mBrightnessLowerBound)) {
             stateBuilder.getBrightnessReason().addModifier(mReason);
@@ -180,14 +185,8 @@
     }
 
     @Override
-    public void onAmbientLuxChange(float ambientLux) {
-        mAmbientLux = ambientLux;
-        recalculateLowerBound();
-    }
-
-    @Override
-    public void setAutoBrightnessState(int state) {
-        mAutoBrightnessEnabled = state == AUTO_BRIGHTNESS_ENABLED;
+    public boolean shouldListenToLightSensor() {
+        return isSettingEnabled();
     }
 
     @Override
@@ -217,6 +216,7 @@
     }
 
     private final class SettingsObserver extends ContentObserver {
+
         SettingsObserver(Handler handler) {
             super(handler);
             mContentResolver.registerContentObserver(
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
index b478952..5661ede 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
@@ -51,4 +51,14 @@
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
         super.dump(ipw);
     }
+
+    @Override
+    public boolean shouldListenToLightSensor() {
+        return false;
+    }
+
+    @Override
+    public void setAmbientLux(float lux) {
+        // unused
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
index db5a524d..be8fa5a 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
@@ -68,14 +68,4 @@
     public void stop() {
         // do nothing
     }
-
-    @Override
-    public void onAmbientLuxChange(float ambientLux) {
-        // do nothing
-    }
-
-    @Override
-    public void setAutoBrightnessState(int state) {
-        // do nothing
-    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java
index 1606159c..fd40ce3 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java
@@ -44,14 +44,15 @@
     void stop();
 
     /**
-     * Allows modifiers to react to ambient lux changes.
-     * @param ambientLux current debounced lux.
+     *
+     * @return whether the brightness state modifier needs to listen to the ambient lux in order to
+     * calculate its bounds.
      */
-    void onAmbientLuxChange(float ambientLux);
+    boolean shouldListenToLightSensor();
 
     /**
-     * Sets the autobrightness state for clampers that need to be aware of the state.
-     * @param state autobrightness state
+     * Current ambient lux
+     * @param lux - ambient lux
      */
-    void setAutoBrightnessState(int state);
+    void setAmbientLux(float lux);
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
index 4ff7bdb..ab880bf 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
@@ -76,4 +76,14 @@
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
         super.dump(ipw);
     }
+
+    @Override
+    public boolean shouldListenToLightSensor() {
+        return false;
+    }
+
+    @Override
+    public void setAmbientLux(float lux) {
+        // unused
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 9e8bf0e..69452310 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -195,6 +195,18 @@
     }
 
     /**
+     * Returns {@link InputMethodInfo} that is queried from {@link #getSelectedMethodId()}.
+     *
+     * @return {@link InputMethodInfo} whose IME ID is the same as {@link #getSelectedMethodId()}.
+     *         {@code null} otherwise
+     */
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    InputMethodInfo getSelectedMethod() {
+        return InputMethodSettingsRepository.get(mUserId).getMethodMap().get(mSelectedMethodId);
+    }
+
+    /**
      * The token we have made for the currently active input method, to
      * identify it in the future.
      */
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 6b15bce..3b0321d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -270,6 +270,10 @@
     private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
 
     private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
+
+    private static final int INVALID_SUBTYPE_HASHCODE =
+            InputMethodSettings.INVALID_SUBTYPE_HASHCODE;
+
     private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
     private static final String HANDLER_THREAD_NAME = "android.imms";
     private static final String PACKAGE_MONITOR_THREAD_NAME = "android.imms2";
@@ -322,7 +326,7 @@
     private final Handler mHandler;
 
     @NonNull
-    private final Handler mPackageMonitorHandler;
+    private final Handler mIoHandler;
 
     @MultiUserUnawareField
     @UserIdInt
@@ -486,10 +490,17 @@
 
     @GuardedBy("ImfLock.class")
     @NonNull
-    InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) {
-        return mUserDataRepository.getOrCreate(userId).mBindingController;
+    UserDataRepository.UserData getUserData(@UserIdInt int userId) {
+        return mUserDataRepository.getOrCreate(userId);
     }
 
+    @GuardedBy("ImfLock.class")
+    @NonNull
+    InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) {
+        return getUserData(userId).mBindingController;
+    }
+
+
     /**
      * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
      * This is to be synchronized with the secure settings keyed with
@@ -1168,7 +1179,7 @@
             final int userId = user.getUserIdentifier();
             SecureSettingsWrapper.onUserStarting(userId);
             synchronized (ImfLock.class) {
-                mService.mUserDataRepository.getOrCreate(userId);
+                mService.getUserData(userId);
                 if (mService.mExperimentalConcurrentMultiUserModeEnabled) {
                     if (mService.mCurrentUserId != userId && mService.mSystemReady) {
                         mService.experimentalInitializeVisibleBackgroundUserLocked(userId);
@@ -1230,7 +1241,7 @@
             Context context,
             boolean experimentalConcurrentMultiUserModeEnabled,
             @Nullable ServiceThread serviceThreadForTesting,
-            @Nullable ServiceThread packageMonitorThreadForTesting,
+            @Nullable ServiceThread ioThreadForTesting,
             @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) {
         synchronized (ImfLock.class) {
             mExperimentalConcurrentMultiUserModeEnabled =
@@ -1251,15 +1262,15 @@
             thread.start();
             mHandler = Handler.createAsync(thread.getLooper(), this);
             {
-                final ServiceThread packageMonitorThread =
-                        packageMonitorThreadForTesting != null
-                                ? packageMonitorThreadForTesting
+                final ServiceThread ioThread =
+                        ioThreadForTesting != null
+                                ? ioThreadForTesting
                                 : new ServiceThread(
                                         PACKAGE_MONITOR_THREAD_NAME,
                                         Process.THREAD_PRIORITY_FOREGROUND,
                                         true /* allowIo */);
-                packageMonitorThread.start();
-                mPackageMonitorHandler = Handler.createAsync(packageMonitorThread.getLooper());
+                ioThread.start();
+                mIoHandler = Handler.createAsync(ioThread.getLooper());
             }
             SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler);
             mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null
@@ -1290,7 +1301,7 @@
                     bindingControllerForTesting != null ? bindingControllerForTesting
                             : bindingControllerFactory);
             for (int id : mUserManagerInternal.getUserIds()) {
-                mUserDataRepository.getOrCreate(id);
+                getUserData(id);
             }
 
             final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
@@ -1530,7 +1541,7 @@
                     }
                 }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
 
-                mMyPackageMonitor.register(mContext, UserHandle.ALL, mPackageMonitorHandler);
+                mMyPackageMonitor.register(mContext, UserHandle.ALL, mIoHandler);
                 mSettingsObserver.registerContentObserverLocked(currentUserId);
 
                 final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
@@ -1998,17 +2009,18 @@
     @GuardedBy("ImfLock.class")
     @NonNull
     InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) {
+        final int userId = mCurrentUserId;
+        final var bindingController = getInputMethodBindingController(userId);
         if (!mBoundToMethod) {
-            getCurMethodLocked().bindInput(mCurClient.mBinding);
+            bindingController.getCurMethod().bindInput(mCurClient.mBinding);
             mBoundToMethod = true;
         }
 
         final boolean restarting = !initial;
         final Binder startInputToken = new Binder();
-        final var bindingController = getInputMethodBindingController(mCurrentUserId);
         final StartInputInfo info = new StartInputInfo(mCurrentUserId,
-                getCurTokenLocked(),
-                getCurTokenDisplayIdLocked(), bindingController.getCurId(), startInputReason,
+                bindingController.getCurToken(), bindingController.getCurTokenDisplayId(),
+                bindingController.getCurId(), startInputReason,
                 restarting, UserHandle.getUserId(mCurClient.mUid),
                 mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo,
                 mImeBindingState.mFocusedWindowSoftInputMode,
@@ -2021,9 +2033,8 @@
         // same-user scenarios.
         // That said ignoring cross-user scenario will never affect IMEs that do not have
         // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
-        if (mCurrentUserId == UserHandle.getUserId(
-                mCurClient.mUid)) {
-            mPackageManagerInternal.grantImplicitAccess(mCurrentUserId, null /* intent */,
+        if (userId == UserHandle.getUserId(mCurClient.mUid)) {
+            mPackageManagerInternal.grantImplicitAccess(userId, null /* intent */,
                     UserHandle.getAppId(bindingController.getCurMethodUid()),
                     mCurClient.mUid, true /* direct */);
         }
@@ -2053,7 +2064,7 @@
         }
 
         final var curId = bindingController.getCurId();
-        final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId)
+        final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(userId)
                 .getMethodMap().get(curId);
         final boolean suppressesSpellChecker =
                 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
@@ -2489,8 +2500,7 @@
 
     @GuardedBy("ImfLock.class")
     void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
-        final var bindingController =
-                mUserDataRepository.getOrCreate(mCurrentUserId).mBindingController;
+        final var bindingController = getInputMethodBindingController(mCurrentUserId);
         bindingController.setSelectedMethodId(null);
 
         // Callback before clean-up binding states.
@@ -3084,8 +3094,9 @@
             throw getExceptionForUnknownImeId(id);
         }
 
+        final var bindingController = getInputMethodBindingController(userId);
         // See if we need to notify a subtype change within the same IME.
-        if (id.equals(getSelectedMethodIdLocked())) {
+        if (id.equals(bindingController.getSelectedMethodId())) {
             final int subtypeCount = info.getSubtypeCount();
             if (subtypeCount <= 0) {
                 notifyInputMethodSubtypeChangedLocked(userId, info, null);
@@ -3120,7 +3131,6 @@
             return;
         }
 
-        final var bindingController = getInputMethodBindingController(userId);
         // Changing to a different IME.
         if (bindingController.getDeviceIdToShowIme() != DEVICE_ID_DEFAULT
                 && deviceId == DEVICE_ID_DEFAULT) {
@@ -3143,7 +3153,7 @@
             // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
             // because mCurMethodId is stored as a history in
             // setSelectedInputMethodAndSubtypeLocked().
-            getInputMethodBindingController(userId).setSelectedMethodId(id);
+            bindingController.setSelectedMethodId(id);
 
             if (mActivityManagerInternal.isSystemReady()) {
                 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
@@ -4186,10 +4196,10 @@
 
     @GuardedBy("ImfLock.class")
     private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) {
-        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        final int userId = mCurrentUserId;
+        final var currentImi = getInputMethodBindingController(userId).getSelectedMethod();
         final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                onlyCurrentIme, settings.getMethodMap().get(getSelectedMethodIdLocked()),
-                mCurrentSubtype);
+                onlyCurrentIme, currentImi, mCurrentSubtype);
         if (nextSubtype == null) {
             return false;
         }
@@ -4204,10 +4214,10 @@
             if (!calledWithValidTokenLocked(token)) {
                 return false;
             }
-            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            final int userId = mCurrentUserId;
+            final var currentImi = getInputMethodBindingController(userId).getSelectedMethod();
             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                    false /* onlyCurrentIme */,
-                    settings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
+                    false /* onlyCurrentIme */, currentImi, mCurrentSubtype);
             return nextSubtype != null;
         }
     }
@@ -4642,8 +4652,7 @@
             if (mCurrentUserId != mSwitchingController.getUserId()) {
                 return;
             }
-            final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
-                    .getMethodMap().get(getSelectedMethodIdLocked());
+            final var imi = getInputMethodBindingController(mCurrentUserId).getSelectedMethod();
             if (imi != null) {
                 mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
             }
@@ -5437,20 +5446,23 @@
                 mCurrentSubtype);
 
         // Set Subtype here
+        final int newSubtypeHashcode;
         if (imi == null || subtypeId < 0) {
-            settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+            newSubtypeHashcode = INVALID_SUBTYPE_HASHCODE;
             mCurrentSubtype = null;
         } else {
             if (subtypeId < imi.getSubtypeCount()) {
                 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
-                settings.putSelectedSubtype(subtype.hashCode());
+                newSubtypeHashcode = subtype.hashCode();
                 mCurrentSubtype = subtype;
             } else {
-                settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+                // TODO(b/347093491): Probably this should be determined from the new subtype.
+                newSubtypeHashcode = INVALID_SUBTYPE_HASHCODE;
                 // If the subtype is not specified, choose the most applicable one
                 mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
             }
         }
+        settings.putSelectedSubtype(newSubtypeHashcode);
         notifyInputMethodSubtypeChangedLocked(settings.getUserId(), imi, mCurrentSubtype);
 
         if (!setSubtypeOnly) {
@@ -5900,27 +5912,36 @@
 
         synchronized (ImfLock.class) {
             final int uid = Binder.getCallingUid();
-            if (getSelectedMethodIdLocked() == null) {
+            final int imeUserId = UserHandle.getUserId(uid);
+            if (imeUserId != mCurrentUserId) {
+                // Currently concurrent multi-user is not supported here due to the remaining
+                // dependency on mCurEditorInfo and mCurClient.
+                // TODO(b/341558132): Remove this early-exit once it becomes multi-user ready.
+                Slog.i(TAG, "Ignoring createInputContentUriToken due to user ID mismatch."
+                        + " imeUserId=" + imeUserId + " mCurrentUserId=" + mCurrentUserId);
                 return null;
             }
-            if (getCurTokenLocked() != token) {
-                Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + getCurTokenLocked()
-                        + " token=" + token);
+            final var bindingController = getInputMethodBindingController(imeUserId);
+            if (bindingController.getSelectedMethodId() == null) {
+                return null;
+            }
+            if (bindingController.getCurToken() != token) {
+                Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken="
+                        + bindingController.getCurToken() + " token=" + token);
                 return null;
             }
             // We cannot simply distinguish a bad IME that reports an arbitrary package name from
             // an unfortunate IME whose internal state is already obsolete due to the asynchronous
             // nature of our system.  Let's compare it with our internal record.
-            final var curPackageName = mCurEditorInfo != null
-                    ? mCurEditorInfo.packageName : null;
+            // TODO(b/341558132): Use "imeUserId" to query per-user "curEditorInfo"
+            final var curPackageName = mCurEditorInfo != null ? mCurEditorInfo.packageName : null;
             if (!TextUtils.equals(curPackageName, packageName)) {
                 Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName="
                         + curPackageName + " packageName=" + packageName);
                 return null;
             }
-            // This user ID can never bee spoofed.
-            final int imeUserId = UserHandle.getUserId(uid);
-            // This user ID can never bee spoofed.
+            // This user ID can never be spoofed.
+            // TODO(b/341558132): Use "imeUserId" to query per-user "curClient"
             final int appUserId = UserHandle.getUserId(mCurClient.mUid);
             // This user ID may be invalid if "contentUri" embedded an invalid user ID.
             final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
@@ -6109,7 +6130,7 @@
             p.println("  mStylusIds=" + (mStylusIds != null
                     ? Arrays.toString(mStylusIds.toArray()) : ""));
             p.println("  mSwitchingController:");
-            mSwitchingController.dump(p);
+            mSwitchingController.dump(p, "    ");
 
             p.println("  mStartInputHistory:");
             mStartInputHistory.dump(pw, "    ");
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index 7ce4074..5569e1e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -58,7 +58,7 @@
      * used {@code -1} here. We cannot change this value as it's already saved into secure settings.
      * </p>
      */
-    private static final int INVALID_SUBTYPE_HASHCODE = -1;
+    static final int INVALID_SUBTYPE_HASHCODE = -1;
     /**
      * A string code that represents "no subtype" when a subtype hashcode is used.
      *
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
index 60b9a4c..68924b5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -78,7 +78,7 @@
                                     userId,
                                     AdditionalSubtypeMapRepository.get(userId),
                                     DirectBootAwareness.AUTO);
-                    sPerUserMap.put(userId, settings);
+                    put(userId, settings);
                 }
             }
         });
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 23f947c..770e3b2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -465,11 +465,11 @@
             return result;
         }
 
-        protected void dump(final Printer pw) {
-            pw.println("    mSwitchingAwareRotationList:");
-            mSwitchingAwareRotationList.dump(pw, "      ");
-            pw.println("    mSwitchingUnawareRotationList:");
-            mSwitchingUnawareRotationList.dump(pw, "      ");
+        protected void dump(@NonNull Printer pw, @NonNull String prefix) {
+            pw.println(prefix + "mSwitchingAwareRotationList:");
+            mSwitchingAwareRotationList.dump(pw, prefix + "  ");
+            pw.println(prefix + "mSwitchingUnawareRotationList:");
+            mSwitchingUnawareRotationList.dump(pw, prefix + "  ");
         }
     }
 
@@ -529,11 +529,11 @@
         return mController.getNextInputMethod(onlyCurrentIme, imi, subtype);
     }
 
-    public void dump(final Printer pw) {
+    public void dump(@NonNull Printer pw, @NonNull String prefix) {
         if (mController != null) {
-            mController.dump(pw);
+            mController.dump(pw, prefix);
         } else {
-            pw.println("    mController=null");
+            pw.println(prefix + "mController=null");
         }
     }
 }
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 880787e..12495bb 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -619,7 +619,18 @@
                 ServiceState state = telephonyManager.getServiceState();
                 if (state != null && state.isUsingNonTerrestrialNetwork()) {
                     networkRequestBuilder.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
-                    networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+                    try {
+                        networkRequestBuilder.addTransportType(NetworkCapabilities
+                                .TRANSPORT_SATELLITE);
+                        networkRequestBuilder.removeCapability(NetworkCapabilities
+                                .NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+                    } catch (IllegalArgumentException ignored) {
+                        // In case TRANSPORT_SATELLITE or NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED
+                        // are not recognized, meaning an old connectivity module runs on new
+                        // android in which case no network with such capabilities will be brought
+                        // up, so it's safe to ignore the exception.
+                        // TODO: Can remove the try-catch in next quarter release.
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java b/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
index 96ab2cc..7dd8f2f 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
@@ -188,7 +188,8 @@
     public static IStreamingResponseCallback wrapWithValidation(
             IStreamingResponseCallback streamingResponseCallback,
             Executor resourceClosingExecutor,
-            AndroidFuture future) {
+            AndroidFuture future,
+            InferenceInfoStore inferenceInfoStore) {
         return new IStreamingResponseCallback.Stub() {
             @Override
             public void onNewContent(Bundle processedResult) throws RemoteException {
@@ -207,6 +208,7 @@
                     sanitizeResponseParams(resultBundle);
                     streamingResponseCallback.onSuccess(resultBundle);
                 } finally {
+                    inferenceInfoStore.addInferenceInfoFromBundle(resultBundle);
                     resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle));
                     future.complete(null);
                 }
@@ -216,6 +218,7 @@
             public void onFailure(int errorCode, String errorMessage,
                     PersistableBundle errorParams) throws RemoteException {
                 streamingResponseCallback.onFailure(errorCode, errorMessage, errorParams);
+                inferenceInfoStore.addInferenceInfoFromBundle(errorParams);
                 future.completeExceptionally(new TimeoutException());
             }
 
@@ -245,7 +248,8 @@
 
     public static IResponseCallback wrapWithValidation(IResponseCallback responseCallback,
             Executor resourceClosingExecutor,
-            AndroidFuture future) {
+            AndroidFuture future,
+            InferenceInfoStore inferenceInfoStore) {
         return new IResponseCallback.Stub() {
             @Override
             public void onSuccess(Bundle resultBundle)
@@ -254,6 +258,7 @@
                     sanitizeResponseParams(resultBundle);
                     responseCallback.onSuccess(resultBundle);
                 } finally {
+                    inferenceInfoStore.addInferenceInfoFromBundle(resultBundle);
                     resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle));
                     future.complete(null);
                 }
@@ -263,6 +268,7 @@
             public void onFailure(int errorCode, String errorMessage,
                     PersistableBundle errorParams) throws RemoteException {
                 responseCallback.onFailure(errorCode, errorMessage, errorParams);
+                inferenceInfoStore.addInferenceInfoFromBundle(errorParams);
                 future.completeExceptionally(new TimeoutException());
             }
 
@@ -291,11 +297,13 @@
 
 
     public static ITokenInfoCallback wrapWithValidation(ITokenInfoCallback responseCallback,
-            AndroidFuture future) {
+            AndroidFuture future,
+            InferenceInfoStore inferenceInfoStore) {
         return new ITokenInfoCallback.Stub() {
             @Override
             public void onSuccess(TokenInfo tokenInfo) throws RemoteException {
                 responseCallback.onSuccess(tokenInfo);
+                inferenceInfoStore.addInferenceInfoFromBundle(tokenInfo.getInfoParams());
                 future.complete(null);
             }
 
@@ -303,6 +311,7 @@
             public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams)
                     throws RemoteException {
                 responseCallback.onFailure(errorCode, errorMessage, errorParams);
+                inferenceInfoStore.addInferenceInfoFromBundle(errorParams);
                 future.completeExceptionally(new TimeoutException());
             }
         };
diff --git a/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java b/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
new file mode 100644
index 0000000..6578853
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.InferenceInfo;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.util.Base64;
+import java.util.Comparator;
+import java.util.List;
+import java.util.TreeSet;
+
+public class InferenceInfoStore {
+    private static final String TAG = "InferenceInfoStore";
+    private final TreeSet<InferenceInfo> inferenceInfos;
+    private final long maxAgeMs;
+
+    public InferenceInfoStore(long maxAgeMs) {
+        this.maxAgeMs = maxAgeMs;
+        this.inferenceInfos = new TreeSet<>(
+                Comparator.comparingLong(InferenceInfo::getStartTimeMs));
+    }
+
+    public List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) {
+        return inferenceInfos.stream().filter(
+                info -> info.getStartTimeMs() > startTimeEpochMillis).toList();
+    }
+
+    public void addInferenceInfoFromBundle(PersistableBundle pb) {
+        if (!pb.containsKey(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY)) {
+            return;
+        }
+
+        try {
+            String infoBytesBase64String = pb.getString(
+                    OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY);
+            if (infoBytesBase64String != null) {
+                byte[] infoBytes = Base64.getDecoder().decode(infoBytesBase64String);
+                com.android.server.ondeviceintelligence.nano.InferenceInfo inferenceInfo =
+                        com.android.server.ondeviceintelligence.nano.InferenceInfo.parseFrom(
+                                infoBytes);
+                add(inferenceInfo);
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Unable to parse InferenceInfo from the received bytes.");
+        }
+    }
+
+    public void addInferenceInfoFromBundle(Bundle b) {
+        if (!b.containsKey(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY)) {
+            return;
+        }
+
+        try {
+            byte[] infoBytes = b.getByteArray(
+                    OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY);
+            if (infoBytes != null) {
+                com.android.server.ondeviceintelligence.nano.InferenceInfo inferenceInfo =
+                        com.android.server.ondeviceintelligence.nano.InferenceInfo.parseFrom(
+                                infoBytes);
+                add(inferenceInfo);
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Unable to parse InferenceInfo from the received bytes.");
+        }
+    }
+
+    private synchronized void add(com.android.server.ondeviceintelligence.nano.InferenceInfo info) {
+        while (System.currentTimeMillis() - inferenceInfos.first().getStartTimeMs() > maxAgeMs) {
+            inferenceInfos.pollFirst();
+        }
+        inferenceInfos.add(toInferenceInfo(info));
+    }
+
+    private static InferenceInfo toInferenceInfo(
+            com.android.server.ondeviceintelligence.nano.InferenceInfo info) {
+        return new InferenceInfo.Builder().setUid(info.uid).setStartTimeMs(
+                info.startTimeMs).setEndTimeMs(info.endTimeMs).setSuspendedTimeMs(
+                info.suspendedTimeMs).build();
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
index 07af8d0..1450dc0 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
@@ -17,5 +17,10 @@
 package com.android.server.ondeviceintelligence;
 
 public interface OnDeviceIntelligenceManagerInternal {
+    /**
+     * Gets the uid for the process that is currently hosting the
+     * {@link android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} registered on
+     * the device.
+     */
     int getInferenceServiceUid();
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 59964e0..9ef2e12 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -44,6 +44,7 @@
 import android.app.ondeviceintelligence.IResponseCallback;
 import android.app.ondeviceintelligence.IStreamingResponseCallback;
 import android.app.ondeviceintelligence.ITokenInfoCallback;
+import android.app.ondeviceintelligence.InferenceInfo;
 import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
 import android.content.ComponentName;
 import android.content.Context;
@@ -127,6 +128,7 @@
     private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
 
     private static final String SYSTEM_PACKAGE = "android";
+    private static final long MAX_AGE_MS = TimeUnit.HOURS.toMillis(3);
 
 
     private final Executor resourceClosingExecutor = Executors.newCachedThreadPool();
@@ -138,7 +140,7 @@
     private final Context mContext;
     protected final Object mLock = new Object();
 
-
+    private final InferenceInfoStore mInferenceInfoStore;
     private RemoteOnDeviceSandboxedInferenceService mRemoteInferenceService;
     private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
     volatile boolean mIsServiceEnabled;
@@ -170,6 +172,7 @@
         super(context);
         mContext = context;
         mTemporaryServiceNames = new String[0];
+        mInferenceInfoStore = new InferenceInfoStore(MAX_AGE_MS);
     }
 
     @Override
@@ -191,6 +194,16 @@
 
             mIsServiceEnabled = isServiceEnabled();
         }
+
+        //connect to remote services(if available) during boot phase.
+        if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+            try {
+                ensureRemoteInferenceServiceInitialized();
+                ensureRemoteIntelligenceServiceInitialized();
+            } catch (Exception e) {
+                Slog.w(TAG, "Couldn't pre-start remote ondeviceintelligence services.", e);
+            }
+        }
     }
 
     private void onDeviceConfigChange(@NonNull Set<String> keys) {
@@ -213,10 +226,18 @@
             }
 
             @Override
+            public List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) {
+                mContext.enforceCallingPermission(
+                        Manifest.permission.DUMP, TAG);
+                return OnDeviceIntelligenceManagerService.this.getLatestInferenceInfo(
+                        startTimeEpochMillis);
+            }
+
+            @Override
             public void getVersion(RemoteCallback remoteCallback) {
                 Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
                 Objects.requireNonNull(remoteCallback);
-                mContext.enforceCallingOrSelfPermission(
+                mContext.enforceCallingPermission(
                         Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
                 if (!mIsServiceEnabled) {
                     Slog.w(TAG, "Service not available");
@@ -241,7 +262,7 @@
                     throws RemoteException {
                 Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
                 Objects.requireNonNull(featureCallback);
-                mContext.enforceCallingOrSelfPermission(
+                mContext.enforceCallingPermission(
                         Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
                 if (!mIsServiceEnabled) {
                     Slog.w(TAG, "Service not available");
@@ -279,7 +300,7 @@
                     throws RemoteException {
                 Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
                 Objects.requireNonNull(listFeaturesCallback);
-                mContext.enforceCallingOrSelfPermission(
+                mContext.enforceCallingPermission(
                         Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
                 if (!mIsServiceEnabled) {
                     Slog.w(TAG, "Service not available");
@@ -323,7 +344,7 @@
                 Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
                 Objects.requireNonNull(feature);
                 Objects.requireNonNull(featureDetailsCallback);
-                mContext.enforceCallingOrSelfPermission(
+                mContext.enforceCallingPermission(
                         Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
                 if (!mIsServiceEnabled) {
                     Slog.w(TAG, "Service not available");
@@ -367,7 +388,7 @@
                 Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
                 Objects.requireNonNull(feature);
                 Objects.requireNonNull(downloadCallback);
-                mContext.enforceCallingOrSelfPermission(
+                mContext.enforceCallingPermission(
                         Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
                 if (!mIsServiceEnabled) {
                     Slog.w(TAG, "Service not available");
@@ -407,7 +428,7 @@
                     sanitizeInferenceParams(request);
                     Objects.requireNonNull(tokenInfoCallback);
 
-                    mContext.enforceCallingOrSelfPermission(
+                    mContext.enforceCallingPermission(
                             Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
                     if (!mIsServiceEnabled) {
                         Slog.w(TAG, "Service not available");
@@ -424,7 +445,8 @@
                                 service.requestTokenInfo(callerUid, feature,
                                         request,
                                         wrapCancellationFuture(cancellationSignalFuture),
-                                        wrapWithValidation(tokenInfoCallback, future));
+                                        wrapWithValidation(tokenInfoCallback, future,
+                                                mInferenceInfoStore));
                                 return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
                             });
                     result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
@@ -450,7 +472,7 @@
                     Objects.requireNonNull(feature);
                     sanitizeInferenceParams(request);
                     Objects.requireNonNull(responseCallback);
-                    mContext.enforceCallingOrSelfPermission(
+                    mContext.enforceCallingPermission(
                             Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
                     if (!mIsServiceEnabled) {
                         Slog.w(TAG, "Service not available");
@@ -470,7 +492,8 @@
                                         wrapCancellationFuture(cancellationSignalFuture),
                                         wrapProcessingFuture(processingSignalFuture),
                                         wrapWithValidation(responseCallback,
-                                                resourceClosingExecutor, future));
+                                                resourceClosingExecutor, future,
+                                                mInferenceInfoStore));
                                 return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
                             });
                     result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
@@ -495,7 +518,7 @@
                     Objects.requireNonNull(feature);
                     sanitizeInferenceParams(request);
                     Objects.requireNonNull(streamingCallback);
-                    mContext.enforceCallingOrSelfPermission(
+                    mContext.enforceCallingPermission(
                             Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
                     if (!mIsServiceEnabled) {
                         Slog.w(TAG, "Service not available");
@@ -515,7 +538,8 @@
                                         wrapCancellationFuture(cancellationSignalFuture),
                                         wrapProcessingFuture(processingSignalFuture),
                                         wrapWithValidation(streamingCallback,
-                                                resourceClosingExecutor, future));
+                                                resourceClosingExecutor, future,
+                                                mInferenceInfoStore));
                                 return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
                             });
                     result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
@@ -836,6 +860,10 @@
                 && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
     }
 
+    private List<InferenceInfo> getLatestInferenceInfo(long startTimeEpochMillis) {
+        return mInferenceInfoStore.getLatestInferenceInfo(startTimeEpochMillis);
+    }
+
     @Nullable
     public String getRemoteConfiguredPackageName() {
         try {
@@ -1056,7 +1084,7 @@
     }
 
     private void setRemoteInferenceServiceUid(int remoteInferenceServiceUid) {
-        synchronized (mLock){
+        synchronized (mLock) {
             this.remoteInferenceServiceUid = remoteInferenceServiceUid;
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 0606563..00e9d8d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -33,6 +33,7 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
 import static android.content.pm.PackageManager.INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_SESSION_INVALID;
 import static android.content.pm.PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
 import static android.content.pm.PackageManager.INSTALL_STAGED;
@@ -3459,11 +3460,6 @@
             }
         }
 
-        if (mHasAppMetadataFile && !getStagedAppMetadataFile().exists()) {
-            throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE,
-                    "App metadata file expected but not found in " + stageDir.getAbsolutePath());
-        }
-
         final List<ApkLite> addedFiles = getAddedApkLitesLocked();
         if (addedFiles.isEmpty()
                 && (removeSplitList.size() == 0 || mHasAppMetadataFile)) {
@@ -3593,6 +3589,13 @@
             }
         }
 
+        File stagedAppMetadataFile = isIncrementalInstallation()
+                ? getTmpAppMetadataFile() : getStagedAppMetadataFile();
+        if (mHasAppMetadataFile && !stagedAppMetadataFile.exists()) {
+            throw new PackageManagerException(INSTALL_FAILED_SESSION_INVALID,
+                    "App metadata file expected but not found in " + stageDir.getAbsolutePath());
+        }
+
         if (isIncrementalInstallation()) {
             if (!isIncrementalInstallationAllowed(existingPkgSetting)) {
                 throw new PackageManagerException(
@@ -3601,8 +3604,8 @@
             }
             // Since we moved the staged app metadata file so that incfs can be initialized, lets
             // now move it back.
-            File appMetadataFile = getTmpAppMetadataFile();
-            if (appMetadataFile.exists()) {
+            if (mHasAppMetadataFile) {
+                File appMetadataFile = getTmpAppMetadataFile();
                 final IncrementalFileStorages incrementalFileStorages =
                         getIncrementalFileStorages();
                 try {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 66a93d7..c0b8034 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3999,7 +3999,9 @@
                 final PackageMetrics.ComponentStateMetrics componentStateMetrics =
                         new PackageMetrics.ComponentStateMetrics(setting,
                                 UserHandle.getUid(userId, packageSetting.getAppId()),
-                                packageSetting.getEnabled(userId), callingUid);
+                                setting.isComponent() ? computer.getComponentEnabledSettingInternal(
+                                        setting.getComponentName(), callingUid, userId)
+                                        : packageSetting.getEnabled(userId), callingUid);
                 if (!setEnabledSettingInternalLocked(computer, packageSetting, setting, userId,
                         callingPackage)) {
                     continue;
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 78d8002..1cd77ff 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -320,10 +320,10 @@
 
     private final Handler mHandler;
 
-    @GuardedBy("itself")
+    @GuardedBy("mServiceLock")
     private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
 
-    @GuardedBy("itself")
+    @GuardedBy("mServiceLock")
     private final ArrayList<LauncherApps.ShortcutChangeCallback> mShortcutChangeCallbacks =
             new ArrayList<>(1);
 
@@ -1847,9 +1847,7 @@
                         return;
                     }
 
-                    synchronized (mListeners) {
-                        copy = new ArrayList<>(mListeners);
-                    }
+                    copy = new ArrayList<>(mListeners);
                 }
                 // Note onShortcutChanged() needs to be called with the system service permissions.
                 for (int i = copy.size() - 1; i >= 0; i--) {
@@ -1874,9 +1872,8 @@
                     if (!isUserUnlockedL(userId)) {
                         return;
                     }
-                    synchronized (mShortcutChangeCallbacks) {
-                        copy = new ArrayList<>(mShortcutChangeCallbacks);
-                    }
+
+                    copy = new ArrayList<>(mShortcutChangeCallbacks);
                 }
                 for (int i = copy.size() - 1; i >= 0; i--) {
                     if (!CollectionUtils.isEmpty(changedList)) {
@@ -3432,7 +3429,7 @@
 
         @Override
         public void addListener(@NonNull ShortcutChangeListener listener) {
-            synchronized (mListeners) {
+            synchronized (mServiceLock) {
                 mListeners.add(Objects.requireNonNull(listener));
             }
         }
@@ -3440,7 +3437,7 @@
         @Override
         public void addShortcutChangeCallback(
                 @NonNull LauncherApps.ShortcutChangeCallback callback) {
-            synchronized (mShortcutChangeCallbacks) {
+            synchronized (mServiceLock) {
                 mShortcutChangeCallbacks.add(Objects.requireNonNull(callback));
             }
         }
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 0052d4f..7f24769 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -543,7 +543,7 @@
             if (!mHalReady.get()) {
                 FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, getCallingUid(),
                             FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__HAL_NOT_READY,
-                            Float.NaN);
+                            Float.NaN, forecastSeconds);
                 return Float.NaN;
             }
 
@@ -553,7 +553,7 @@
                 }
                 FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, getCallingUid(),
                             FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__INVALID_ARGUMENT,
-                            Float.NaN);
+                            Float.NaN, forecastSeconds);
                 return Float.NaN;
             }
 
@@ -1778,7 +1778,7 @@
                     FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED,
                             Binder.getCallingUid(),
                             FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__NO_TEMPERATURE,
-                            Float.NaN);
+                            Float.NaN, forecastSeconds);
                     return Float.NaN;
                 }
 
@@ -1789,7 +1789,7 @@
                     FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED,
                             Binder.getCallingUid(),
                             THERMAL_HEADROOM_CALLED__API_STATUS__NO_TEMPERATURE_THRESHOLD,
-                            Float.NaN);
+                            Float.NaN, forecastSeconds);
                     return Float.NaN;
                 }
 
@@ -1828,12 +1828,12 @@
                     FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED,
                             Binder.getCallingUid(),
                             THERMAL_HEADROOM_CALLED__API_STATUS__NO_TEMPERATURE_THRESHOLD,
-                            Float.NaN);
+                            Float.NaN, forecastSeconds);
                 } else {
                     FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED,
                             Binder.getCallingUid(),
                             FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__SUCCESS,
-                            maxNormalized);
+                            maxNormalized, forecastSeconds);
                 }
                 return maxNormalized;
             }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index edc5696..5159fc4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2983,7 +2983,7 @@
 
     void removeStartingWindowAnimation(boolean prepareAnimation) {
         mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
-        if (task != null) {
+        if (mStartingData != null && task != null) {
             task.mSharedStartingData = null;
         }
         if (mStartingWindow == null) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a3a6b51..87ee5d8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5719,15 +5719,21 @@
      * @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
      */
     boolean supportsSystemDecorations() {
+        boolean forceDesktopModeOnDisplay = forceDesktopMode();
+
+        if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) {
+            // System decorations should not be forced on a rear display due to security policies.
+            forceDesktopModeOnDisplay =
+                    forceDesktopModeOnDisplay && ((mDisplay.getFlags() & Display.FLAG_REAR) == 0);
+        }
+
         return (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
                 || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
-                || forceDesktopMode())
+                || forceDesktopModeOnDisplay)
                 // VR virtual display will be used to run and render 2D app within a VR experience.
                 && mDisplayId != mWmService.mVr2dDisplayId
                 // Do not show system decorations on untrusted virtual display.
-                && isTrusted()
-                // No system decoration on rear display.
-                && (mDisplay.getFlags() & Display.FLAG_REAR) == 0;
+                && isTrusted();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index b2ba9d1..bf99ccd 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
 
@@ -35,6 +36,7 @@
 import android.content.IntentFilter;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.os.Binder;
 import android.os.Bundle;
@@ -60,6 +62,7 @@
 import android.view.animation.Interpolator;
 import android.widget.Button;
 import android.widget.FrameLayout;
+import android.widget.RelativeLayout;
 
 import com.android.internal.R;
 
@@ -233,6 +236,7 @@
                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                 PixelFormat.TRANSLUCENT);
         lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars());
+        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         // Trusted overlay so touches outside the touchable area are allowed to pass through
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
                 | WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
@@ -245,13 +249,20 @@
 
     private FrameLayout.LayoutParams getBubbleLayoutParams() {
         return new FrameLayout.LayoutParams(
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.immersive_mode_cling_width),
+                getClingWindowWidth(),
                 ViewGroup.LayoutParams.WRAP_CONTENT,
                 Gravity.CENTER_HORIZONTAL | Gravity.TOP);
     }
 
     /**
+     * Returns the width of the cling window.
+     */
+    private int getClingWindowWidth() {
+        return mContext.getResources().getDimensionPixelSize(
+                R.dimen.immersive_mode_cling_width);
+    }
+
+    /**
      * @return the window token that's used by all ImmersiveModeConfirmation windows.
      */
     IBinder getWindowToken() {
@@ -387,6 +398,24 @@
 
         @Override
         public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+            // If the top display cutout overlaps with the full-width (windowWidth=-1)/centered
+            // dialog, then adjust the dialog contents by the cutout
+            final int width = getWidth();
+            final int windowWidth = getClingWindowWidth();
+            final Rect topDisplayCutout = insets.getDisplayCutout() != null
+                    ? insets.getDisplayCutout().getBoundingRectTop()
+                    : new Rect();
+            final boolean intersectsTopCutout = topDisplayCutout.intersects(
+                    width - (windowWidth / 2), 0,
+                    width + (windowWidth / 2), topDisplayCutout.bottom);
+            if (mClingWindow != null &&
+                    (windowWidth < 0 || (width > 0 && intersectsTopCutout))) {
+                final View iconView = mClingWindow.findViewById(R.id.immersive_cling_icon);
+                RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)
+                        iconView.getLayoutParams();
+                lp.topMargin = topDisplayCutout.bottom;
+                iconView.setLayoutParams(lp);
+            }
             // we will be hiding the nav bar, so layout as if it's already hidden
             return new WindowInsets.Builder(insets).setInsets(
                     Type.systemBars(), Insets.NONE).build();
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 3b25cb1..42bd75a 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -127,7 +127,7 @@
     protected IInputMethodInvoker mMockInputMethodInvoker;
     protected InputMethodManagerService mInputMethodManagerService;
     protected ServiceThread mServiceThread;
-    protected ServiceThread mPackageMonitorThread;
+    protected ServiceThread mIoThread;
     protected boolean mIsLargeScreen;
     private InputManagerGlobal.TestSession mInputManagerGlobalSession;
 
@@ -226,14 +226,14 @@
                         "immstest1",
                         Process.THREAD_PRIORITY_FOREGROUND,
                         true /* allowIo */);
-        mPackageMonitorThread =
+        mIoThread =
                 new ServiceThread(
                         "immstest2",
                         Process.THREAD_PRIORITY_FOREGROUND,
                         true /* allowIo */);
         mInputMethodManagerService = new InputMethodManagerService(mContext,
                 InputMethodManagerService.shouldEnableExperimentalConcurrentMultiUserMode(mContext),
-                mServiceThread, mPackageMonitorThread,
+                mServiceThread, mIoThread,
                 unusedUserId -> mMockInputMethodBindingController);
         spyOn(mInputMethodManagerService);
 
@@ -267,8 +267,8 @@
             mInputMethodManagerService.mInputMethodDeviceConfigs.destroy();
         }
 
-        if (mPackageMonitorThread != null) {
-            mPackageMonitorThread.quitSafely();
+        if (mIoThread != null) {
+            mIoThread.quitSafely();
         }
 
         if (mServiceThread != null) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index d3efcb6..2a458c42 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -52,7 +52,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.display.brightness.clamper.BrightnessClamperController;
 import com.android.server.display.config.HysteresisLevels;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.testutils.OffsettableClock;
@@ -102,8 +101,6 @@
     @Mock Handler mNoOpHandler;
     @Mock BrightnessRangeController mBrightnessRangeController;
     @Mock
-    BrightnessClamperController mBrightnessClamperController;
-    @Mock
     DisplayManagerFlags mDisplayManagerFlags;
     @Mock BrightnessThrottler mBrightnessThrottler;
 
@@ -181,7 +178,7 @@
                 mContext, mBrightnessRangeController, mBrightnessThrottler,
                 useHorizon ? AMBIENT_LIGHT_HORIZON_SHORT : 1,
                 useHorizon ? AMBIENT_LIGHT_HORIZON_LONG : 10000, userLux, userNits,
-                mBrightnessClamperController, mDisplayManagerFlags
+                mDisplayManagerFlags
         );
 
         when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index efa224f..95f0b65 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1415,7 +1415,7 @@
         when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
         when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
         when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
-        when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+        when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer(
                 invocation -> DisplayBrightnessState.builder()
                         .setIsSlowChange(invocation.getArgument(2))
                         .setBrightness(invocation.getArgument(1))
@@ -1439,7 +1439,7 @@
         when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
         when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
         when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
-        when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+        when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer(
                 invocation -> DisplayBrightnessState.builder()
                         .setIsSlowChange(invocation.getArgument(2))
                         .setBrightness(invocation.getArgument(1))
@@ -2094,7 +2094,7 @@
         BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
 
         when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
-        when(clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+        when(clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer(
                 invocation -> DisplayBrightnessState.builder()
                         .setIsSlowChange(invocation.getArgument(2))
                         .setBrightness(invocation.getArgument(1))
@@ -2328,7 +2328,7 @@
         BrightnessClamperController getBrightnessClamperController(Handler handler,
                 BrightnessClamperController.ClamperChangeListener clamperChangeListener,
                 BrightnessClamperController.DisplayDeviceData data, Context context,
-                DisplayManagerFlags flags) {
+                DisplayManagerFlags flags, SensorManager sensorManager) {
             return mClamperController;
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 5487bc5..69043f5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -16,14 +16,20 @@
 
 package com.android.server.display.brightness.clamper;
 
+import static android.view.Display.STATE_ON;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
 import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Handler;
@@ -34,11 +40,15 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.TestUtils;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.testutils.OffsettableClock;
 import com.android.server.testutils.TestHandler;
 
 import org.junit.Before;
@@ -54,13 +64,16 @@
 public class BrightnessClamperControllerTest {
     private static final float FLOAT_TOLERANCE = 0.001f;
 
-    private final TestHandler mTestHandler = new TestHandler(null);
+    private final OffsettableClock mClock = new OffsettableClock();
+    private final TestHandler mTestHandler = new TestHandler(null, mClock);
 
     @Rule
     public final TestableContext mMockContext = new TestableContext(
             InstrumentationRegistry.getInstrumentation().getContext());
     @Mock
     private BrightnessClamperController.ClamperChangeListener mMockExternalListener;
+    @Mock
+    private SensorManager mSensorManager;
 
     @Mock
     private BrightnessClamperController.DisplayDeviceData mMockDisplayDeviceData;
@@ -74,15 +87,25 @@
     private BrightnessModifier mMockModifier;
     @Mock
     private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
+
+    Sensor mLightSensor;
+
     @Mock
     private DeviceConfig.Properties mMockProperties;
     private BrightnessClamperController mClamperController;
     private TestInjector mTestInjector;
 
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
         mTestInjector = new TestInjector(List.of(mMockClamper), List.of(mMockModifier));
+        when(mSensorManager.getDefaultSensor(anyInt())).thenReturn(mLightSensor);
+        when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+
         mClamperController = createBrightnessClamperController();
     }
 
@@ -132,7 +155,7 @@
     public void testClamp_AppliesModifier() {
         float initialBrightness = 0.2f;
         boolean initialSlowChange = true;
-        mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange);
+        mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
 
         verify(mMockModifier).apply(eq(mMockRequest), any());
     }
@@ -151,7 +174,7 @@
         mTestHandler.flush();
 
         DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
-                initialSlowChange);
+                initialSlowChange, STATE_ON);
 
         assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
         assertEquals(PowerManager.BRIGHTNESS_MAX, state.getMaxBrightness(), FLOAT_TOLERANCE);
@@ -175,7 +198,7 @@
         mTestHandler.flush();
 
         DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
-                initialSlowChange);
+                initialSlowChange, STATE_ON);
 
         assertEquals(clampedBrightness, state.getBrightness(), FLOAT_TOLERANCE);
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
@@ -199,7 +222,7 @@
         mTestHandler.flush();
 
         DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
-                initialSlowChange);
+                initialSlowChange, STATE_ON);
 
         assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
@@ -223,10 +246,10 @@
         mTestHandler.flush();
         // first call of clamp method
         mClamperController.clamp(mMockRequest, initialBrightness,
-                initialSlowChange);
+                initialSlowChange, STATE_ON);
         // immediately second call of clamp method
         DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
-                initialSlowChange);
+                initialSlowChange, STATE_ON);
 
         assertEquals(clampedBrightness, state.getBrightness(), FLOAT_TOLERANCE);
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
@@ -237,6 +260,31 @@
     }
 
     @Test
+    public void testAmbientLuxChanges() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(
+                SensorEventListener.class);
+
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                anyInt(), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL))).thenReturn(List.of(mLightSensor));
+
+        float initialBrightness = 0.8f;
+        boolean initialSlowChange = true;
+
+        DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
+                initialSlowChange, STATE_ON);
+        assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
+
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 50, mClock.now()));
+        verify(mMockModifier).setAmbientLux(50);
+
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 300, mClock.now()));
+        verify(mMockModifier).setAmbientLux(300);
+    }
+
+    @Test
     public void testStop() {
         mClamperController.stop();
         verify(mMockModifier).stop();
@@ -245,7 +293,7 @@
 
     private BrightnessClamperController createBrightnessClamperController() {
         return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener,
-                mMockDisplayDeviceData, mMockContext, mFlags);
+                mMockDisplayDeviceData, mMockContext, mFlags, mSensorManager);
     }
 
     private class TestInjector extends BrightnessClamperController.Injector {
@@ -282,7 +330,7 @@
         @Override
         List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
                 Handler handler, BrightnessClamperController.ClamperChangeListener listener,
-                DisplayDeviceConfig displayDeviceConfig) {
+                DisplayDeviceConfig displayDeviceConfig, SensorManager sensorManager) {
             return mModifiers;
         }
     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
index 21e066d..d672435 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
@@ -20,8 +20,6 @@
 import android.provider.Settings
 import android.testing.TestableContext
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED
-import com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
 import com.android.server.display.DisplayDeviceConfig
 import com.android.server.display.brightness.BrightnessReason
 import com.android.server.display.feature.flags.Flags
@@ -54,10 +52,12 @@
     @Before
     fun setUp() {
         modifier =
-            BrightnessLowLuxModifier(testHandler,
+            BrightnessLowLuxModifier(
+                testHandler,
                 mockClamperChangeListener,
                 context,
-                mockDisplayDeviceConfig)
+                mockDisplayDeviceConfig
+            )
 
         // values below transition point (even dimmer range)
         // nits: 0.1 -> backlight 0.02 -> brightness -> 0.1
@@ -66,7 +66,7 @@
         whenever(mockDisplayDeviceConfig.getBrightnessFromBacklight(/* backlight = */ 0.02f))
                 .thenReturn(LOW_LUX_BRIGHTNESS)
 
-        // values above transition point (noraml range)
+        // values above transition point (normal range)
         // nits: 10 -> backlight 0.2 -> brightness -> 0.3
         whenever(mockDisplayDeviceConfig.getBacklightFromNits(/* nits= */ 2f))
                 .thenReturn(0.15f)
@@ -95,12 +95,12 @@
         // test transition point ensures brightness doesn't drop when setting is off.
         Settings.Secure.putIntForUser(context.contentResolver,
             Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID)
-        modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
         modifier.recalculateLowerBound()
         testHandler.flush()
         assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
         assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - ie off
-        modifier.onAmbientLuxChange(3000.0f)
+        modifier.setAmbientLux(3000f)
+
         testHandler.flush()
         assertThat(modifier.isActive).isFalse()
         assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
@@ -115,8 +115,8 @@
             Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID)
         Settings.Secure.putFloatForUser(context.contentResolver,
             Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.1f, USER_ID)
-        modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
-        modifier.onAmbientLuxChange(400.0f)
+        modifier.setAmbientLux(400f)
+
         testHandler.flush()
 
         assertThat(modifier.isActive).isTrue()
@@ -133,7 +133,6 @@
             Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID)
         Settings.Secure.putFloatForUser(context.contentResolver,
             Settings.Secure.EVEN_DIMMER_MIN_NITS, 10.0f, USER_ID)
-        modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
         modifier.recalculateLowerBound()
         testHandler.flush()
 
@@ -152,8 +151,8 @@
             Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
         Settings.Secure.putFloatForUser(context.contentResolver,
             Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
-        modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
-        modifier.onAmbientLuxChange(400.0f)
+        modifier.setAmbientLux(400f)
+
         testHandler.flush()
 
         assertThat(modifier.isActive).isTrue()
@@ -180,8 +179,8 @@
             Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID) // off
         Settings.Secure.putFloatForUser(context.contentResolver,
             Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
-        modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
-        modifier.onAmbientLuxChange(400.0f)
+        modifier.setAmbientLux(400f)
+
         testHandler.flush()
 
         assertThat(modifier.isActive).isFalse()
@@ -203,15 +202,14 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
-    fun testDisabledWhenAutobrightnessIsOff() {
+    fun testEnabledEvenWhenAutobrightnessIsOff() {
         // test that high lux prevents low brightness range.
         Settings.Secure.putIntForUser(context.contentResolver,
             Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
         Settings.Secure.putFloatForUser(context.contentResolver,
             Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
 
-        modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
-        modifier.onAmbientLuxChange(400.0f)
+        modifier.setAmbientLux(400f)
         testHandler.flush()
 
         assertThat(modifier.isActive).isTrue()
@@ -219,15 +217,13 @@
         assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
         assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
 
-
-        modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_DISABLED)
-        modifier.onAmbientLuxChange(400.0f)
+        modifier.setAmbientLux(400f)
         testHandler.flush()
 
-        assertThat(modifier.isActive).isFalse()
+        assertThat(modifier.isActive).isTrue()
         // Test restriction from lux setting
-        assertThat(modifier.brightnessReason).isEqualTo(0)
-        assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
+        assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
+        assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
     }
 }
 
diff --git a/services/tests/ondeviceintelligencetests/OWNERS b/services/tests/ondeviceintelligencetests/OWNERS
new file mode 100644
index 0000000..09774f7
--- /dev/null
+++ b/services/tests/ondeviceintelligencetests/OWNERS
@@ -0,0 +1 @@
+file:/core/java/android/app/ondeviceintelligence/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index f221b75..148c968 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -18,6 +18,20 @@
 
 import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
+import static android.app.StatusBarManager.DISABLE2_MASK;
+import static android.app.StatusBarManager.DISABLE2_NONE;
+import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE;
+import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
+import static android.app.StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
+import static android.app.StatusBarManager.DISABLE_BACK;
+import static android.app.StatusBarManager.DISABLE_CLOCK;
+import static android.app.StatusBarManager.DISABLE_HOME;
+import static android.app.StatusBarManager.DISABLE_MASK;
+import static android.app.StatusBarManager.DISABLE_NONE;
+import static android.app.StatusBarManager.DISABLE_RECENT;
+import static android.app.StatusBarManager.DISABLE_SEARCH;
+import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -137,6 +151,7 @@
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
 
         when(mMockStatusBar.asBinder()).thenReturn(mMockStatusBar);
+        when(mMockStatusBar.isBinderAlive()).thenReturn(true);
         when(mApplicationInfo.loadLabel(any())).thenReturn(APP_NAME);
         mockHandleIncomingUser();
 
@@ -722,6 +737,369 @@
         verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt());
     }
 
+    @Test
+    public void testGetDisableFlags() throws Exception {
+        String packageName = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        mStatusBarManagerService.disable(DISABLE_NONE, mMockStatusBar, packageName);
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+    }
+
+    @Test
+    public void testSetHomeDisabled() throws Exception {
+        int expectedFlags = DISABLE_MASK & DISABLE_HOME;
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+        // disable
+        mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg);
+        // check that disable works
+        assertEquals(expectedFlags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+    }
+
+    @Test
+    public void testSetSystemInfoDisabled() throws Exception {
+        int expectedFlags = DISABLE_MASK & DISABLE_SYSTEM_INFO;
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+        // disable
+        mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(expectedFlags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+    }
+
+    @Test
+    public void testSetRecentDisabled() throws Exception {
+        int expectedFlags = DISABLE_MASK & DISABLE_RECENT;
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+        // disable
+        mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(expectedFlags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+    }
+
+    @Test
+    public void testSetBackDisabled() throws Exception {
+        int expectedFlags = DISABLE_MASK & DISABLE_BACK;
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+        // disable
+        mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(expectedFlags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+    }
+
+    @Test
+    public void testSetClockDisabled() throws Exception {
+        int expectedFlags = DISABLE_MASK & DISABLE_CLOCK;
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+        // disable home
+        mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(expectedFlags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+    }
+
+    @Test
+    public void testSetSearchDisabled() throws Exception {
+        int expectedFlags = DISABLE_MASK & DISABLE_SEARCH;
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE, mStatusBarManagerService.getDisableFlags(mMockStatusBar,
+                userId)[0]);
+        // disable
+        mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(expectedFlags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+    }
+
+    @Test
+    public void testSetQuickSettingsDisabled2() throws Exception {
+        int expectedFlags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS;
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE2_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+        // disable
+        mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(expectedFlags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+    }
+
+    @Test
+    public void testSetSystemIconsDisabled2() throws Exception {
+        int expectedFlags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS;
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE, mStatusBarManagerService.getDisableFlags(mMockStatusBar,
+                userId)[1]);
+        // disable
+        mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(expectedFlags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+    }
+
+    @Test
+    public void testSetNotificationShadeDisabled2() throws Exception {
+        int expectedFlags = DISABLE2_MASK & DISABLE2_NOTIFICATION_SHADE;
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE, mStatusBarManagerService.getDisableFlags(mMockStatusBar,
+                userId)[1]);
+        // disable
+        mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(expectedFlags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+    }
+
+
+    @Test
+    public void testSetGlobalActionsDisabled2() throws Exception {
+        int expectedFlags = DISABLE2_MASK & DISABLE2_GLOBAL_ACTIONS;
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE, mStatusBarManagerService.getDisableFlags(mMockStatusBar,
+                userId)[1]);
+        // disable
+        mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(expectedFlags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+    }
+
+    @Test
+    public void testSetRotateSuggestionsDisabled2() throws Exception {
+        int expectedFlags = DISABLE2_MASK & DISABLE2_ROTATE_SUGGESTIONS;
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+        // disable
+        mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(expectedFlags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+    }
+
+    @Test
+    public void testSetTwoDisable2Flags() throws Exception {
+        int expectedFlags = DISABLE2_MASK & DISABLE2_ROTATE_SUGGESTIONS & DISABLE2_QUICK_SETTINGS;
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+        // disable
+        mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(expectedFlags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+    }
+
+    @Test
+    public void testSetTwoDisableFlagsRemoveOne() throws Exception {
+        int twoFlags = DISABLE_MASK & DISABLE_HOME & DISABLE_BACK;
+        int expectedFlag = DISABLE_MASK & DISABLE_HOME;
+
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+        // disable
+        mStatusBarManagerService.disable(twoFlags, mMockStatusBar, pkg);
+        mStatusBarManagerService.disable(DISABLE_NONE, mMockStatusBar, pkg);
+        mStatusBarManagerService.disable(expectedFlag, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(expectedFlag,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+    }
+
+    @Test
+    public void testSetTwoDisable2FlagsRemoveOne() throws Exception {
+        int twoFlags = DISABLE2_MASK & DISABLE2_ROTATE_SUGGESTIONS & DISABLE2_QUICK_SETTINGS;
+        int expectedFlag = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS;
+
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE, mStatusBarManagerService.getDisableFlags(mMockStatusBar,
+                userId)[1]);
+        // disable
+        mStatusBarManagerService.disable2(twoFlags, mMockStatusBar, pkg);
+        mStatusBarManagerService.disable2(DISABLE2_NONE, mMockStatusBar, pkg);
+        mStatusBarManagerService.disable2(expectedFlag, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(expectedFlag,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+    }
+
+    @Test
+    public void testDisableBothFlags() throws Exception {
+        int disableFlags = DISABLE_MASK & DISABLE_BACK & DISABLE_HOME;
+        int disable2Flags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS & DISABLE2_ROTATE_SUGGESTIONS;
+
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE, mStatusBarManagerService.getDisableFlags(mMockStatusBar,
+                userId)[0]);
+        assertEquals(DISABLE2_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+        // disable
+        mStatusBarManagerService.disable(disableFlags, mMockStatusBar, pkg);
+        mStatusBarManagerService.disable2(disable2Flags, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(disableFlags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+        assertEquals(disable2Flags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+    }
+
+    @Test
+    public void testDisableBothFlagsEnable1Flags() throws Exception {
+        int disableFlags = DISABLE_MASK & DISABLE_BACK & DISABLE_HOME;
+        int disable2Flags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS & DISABLE2_ROTATE_SUGGESTIONS;
+
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+        assertEquals(DISABLE2_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+        // disable
+        mStatusBarManagerService.disable(disableFlags, mMockStatusBar, pkg);
+        mStatusBarManagerService.disable2(disable2Flags, mMockStatusBar, pkg);
+        // re-enable one
+        mStatusBarManagerService.disable(DISABLE_NONE, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+        assertEquals(disable2Flags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+    }
+
+    @Test
+    public void testDisableBothFlagsEnable2Flags() throws Exception {
+        int disableFlags = DISABLE_MASK & DISABLE_BACK & DISABLE_HOME;
+        int disable2Flags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS & DISABLE2_ROTATE_SUGGESTIONS;
+
+        String pkg = mContext.getPackageName();
+        int userId = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(userId);
+        // before disabling
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+        assertEquals(DISABLE2_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+        // disable
+        mStatusBarManagerService.disable(disableFlags, mMockStatusBar, pkg);
+        mStatusBarManagerService.disable2(disable2Flags, mMockStatusBar, pkg);
+        // re-enable one
+        mStatusBarManagerService.disable2(DISABLE_NONE, mMockStatusBar, pkg);
+        // check that right flag is disabled
+        assertEquals(disableFlags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[0]);
+        assertEquals(DISABLE2_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, userId)[1]);
+    }
+
+    @Test
+    public void testDifferentUsersDisable() throws Exception {
+        int user1Id = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(user1Id);
+        int user2Id = 14;
+        mockComponentInfo(user2Id);
+        mockEverything(user2Id);
+
+        int expectedUser1Flags = DISABLE_MASK & DISABLE_BACK;
+        int expectedUser2Flags = DISABLE_MASK & DISABLE_HOME;
+        String pkg = mContext.getPackageName();
+
+        // before disabling
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, user2Id)[0]);
+        // disable
+        mStatusBarManagerService.disableForUser(expectedUser1Flags, mMockStatusBar, pkg, user1Id);
+        mStatusBarManagerService.disableForUser(expectedUser2Flags, mMockStatusBar, pkg, user2Id);
+        // check that right flag is disabled
+        assertEquals(expectedUser1Flags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
+        assertEquals(expectedUser2Flags,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, user2Id)[0]);
+    }
+
+
     private void mockUidCheck() {
         mockUidCheck(TEST_PACKAGE);
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 15c9bfb..f07e5bc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -511,7 +511,8 @@
     TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker;
 
     TestableFlagResolver mTestFlagResolver = new TestableFlagResolver();
-    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
             1 << 30);
     @Mock
@@ -626,7 +627,7 @@
         when(mPackageManagerClient.getPackageUidAsUser(any(), anyInt())).thenReturn(mUid);
         when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenAnswer(
                 (Answer<Boolean>) invocation -> {
-                    // TODO: b/317957802 - This is overly broad and basically makes ANY 
+                    // TODO: b/317957802 - This is overly broad and basically makes ANY
                     //  isSameApp() check pass,  requiring Mockito.reset() for meaningful
                     //  tests! Make it more precise.
                     Object[] args = invocation.getArguments();
@@ -6892,22 +6893,15 @@
         verify(visitor, times(1)).accept(eq(personIcon3.getUri()));
     }
 
-    private PendingIntent getPendingIntentWithUri(Uri uri) {
-        return PendingIntent.getActivity(mContext, 0,
-                new Intent("action", uri),
-                PendingIntent.FLAG_IMMUTABLE);
-    }
-
     @Test
-    @EnableFlags({android.app.Flags.FLAG_VISIT_RISKY_URIS})
-    public void testVisitUris_callStyle_ongoingCall() {
+    public void testVisitUris_callStyle() {
         Icon personIcon = Icon.createWithContentUri("content://media/person");
         Icon verificationIcon = Icon.createWithContentUri("content://media/verification");
         Person callingPerson = new Person.Builder().setName("Someone")
                 .setIcon(personIcon)
                 .build();
-        Uri hangUpUri = Uri.parse("content://intent/hangup");
-        PendingIntent hangUpIntent = getPendingIntentWithUri(hangUpUri);
+        PendingIntent hangUpIntent = PendingIntent.getActivity(mContext, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         Notification n = new Notification.Builder(mContext, "a")
                 .setStyle(Notification.CallStyle.forOngoingCall(callingPerson, hangUpIntent)
                         .setVerificationIcon(verificationIcon))
@@ -6920,42 +6914,10 @@
 
         verify(visitor, times(1)).accept(eq(personIcon.getUri()));
         verify(visitor, times(1)).accept(eq(verificationIcon.getUri()));
-        verify(visitor, times(1)).accept(eq(hangUpUri));
         hangUpIntent.cancel();
     }
 
     @Test
-    @EnableFlags({android.app.Flags.FLAG_VISIT_RISKY_URIS})
-    public void testVisitUris_callStyle_incomingCall() {
-        Icon personIcon = Icon.createWithContentUri("content://media/person");
-        Icon verificationIcon = Icon.createWithContentUri("content://media/verification");
-        Person callingPerson = new Person.Builder().setName("Someone")
-                .setIcon(personIcon)
-                .build();
-        Uri answerUri = Uri.parse("content://intent/answer");
-        PendingIntent answerIntent = getPendingIntentWithUri(answerUri);
-        Uri declineUri = Uri.parse("content://intent/decline");
-        PendingIntent declineIntent = getPendingIntentWithUri(declineUri);
-        Notification n = new Notification.Builder(mContext, "a")
-                .setStyle(Notification.CallStyle.forIncomingCall(callingPerson, declineIntent,
-                                answerIntent)
-                        .setVerificationIcon(verificationIcon))
-                .setContentTitle("Calling...")
-                .setSmallIcon(android.R.drawable.sym_def_app_icon)
-                .build();
-
-        Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
-        n.visitUris(visitor);
-
-        verify(visitor, times(1)).accept(eq(personIcon.getUri()));
-        verify(visitor, times(1)).accept(eq(verificationIcon.getUri()));
-        verify(visitor, times(1)).accept(eq(answerIntent.getIntent().getData()));
-        verify(visitor, times(1)).accept(eq(declineUri));
-        answerIntent.cancel();
-        declineIntent.cancel();
-    }
-
-    @Test
     public void testVisitUris_styleExtrasWithoutStyle() {
         Notification.Builder notification = new Notification.Builder(mContext, "a")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
@@ -7001,87 +6963,23 @@
     }
 
     @Test
-    @EnableFlags({android.app.Flags.FLAG_VISIT_RISKY_URIS})
     public void testVisitUris_wearableExtender() {
         Icon actionIcon = Icon.createWithContentUri("content://media/action");
         Icon wearActionIcon = Icon.createWithContentUri("content://media/wearAction");
-        Uri displayIntentUri = Uri.parse("content://intent/display");
-        PendingIntent displayIntent = getPendingIntentWithUri(displayIntentUri);
-        Uri actionIntentUri = Uri.parse("content://intent/action");
-        PendingIntent actionIntent = getPendingIntentWithUri(actionIntentUri);
-        Uri wearActionIntentUri = Uri.parse("content://intent/wear");
-        PendingIntent wearActionIntent = getPendingIntentWithUri(wearActionIntentUri);
+        PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         Notification n = new Notification.Builder(mContext, "a")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
-                .addAction(
-                        new Notification.Action.Builder(actionIcon, "Hey!", actionIntent).build())
-                .extend(new Notification.WearableExtender()
-                        .setDisplayIntent(displayIntent)
-                        .addAction(new Notification.Action.Builder(wearActionIcon, "Wear!",
-                                wearActionIntent)
-                                .build()))
+                .addAction(new Notification.Action.Builder(actionIcon, "Hey!", intent).build())
+                .extend(new Notification.WearableExtender().addAction(
+                        new Notification.Action.Builder(wearActionIcon, "Wear!", intent).build()))
                 .build();
 
         Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
         n.visitUris(visitor);
 
         verify(visitor).accept(eq(actionIcon.getUri()));
-        verify(visitor, times(1)).accept(eq(actionIntentUri));
         verify(visitor).accept(eq(wearActionIcon.getUri()));
-        verify(visitor, times(1)).accept(eq(wearActionIntentUri));
-        displayIntent.cancel();
-        actionIntent.cancel();
-        wearActionIntent.cancel();
-    }
-
-    @Test
-    @EnableFlags({android.app.Flags.FLAG_VISIT_RISKY_URIS})
-    public void testVisitUris_tvExtender() {
-        Uri contentIntentUri = Uri.parse("content://intent/content");
-        PendingIntent contentIntent = getPendingIntentWithUri(contentIntentUri);
-        Uri deleteIntentUri = Uri.parse("content://intent/delete");
-        PendingIntent deleteIntent = getPendingIntentWithUri(deleteIntentUri);
-        Notification n = new Notification.Builder(mContext, "a")
-                .extend(
-                        new Notification.TvExtender()
-                                .setContentIntent(contentIntent)
-                                .setDeleteIntent(deleteIntent))
-                .build();
-
-        Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
-        n.visitUris(visitor);
-
-        verify(visitor, times(1)).accept(eq(contentIntentUri));
-        verify(visitor, times(1)).accept(eq(deleteIntentUri));
-        contentIntent.cancel();
-        deleteIntent.cancel();
-    }
-
-    @Test
-    @EnableFlags({android.app.Flags.FLAG_VISIT_RISKY_URIS})
-    public void testVisitUris_carExtender() {
-        final String testParticipant = "testParticipant";
-        Uri readPendingIntentUri = Uri.parse("content://intent/read");
-        PendingIntent readPendingIntent = getPendingIntentWithUri(readPendingIntentUri);
-        Uri replyPendingIntentUri = Uri.parse("content://intent/reply");
-        PendingIntent replyPendingIntent = getPendingIntentWithUri(replyPendingIntentUri);
-        final RemoteInput testRemoteInput = new RemoteInput.Builder("key").build();
-
-        Notification n = new Notification.Builder(mContext, "a")
-                .extend(new Notification.CarExtender().setUnreadConversation(
-                        new Notification.CarExtender.Builder(testParticipant)
-                                .setReadPendingIntent(readPendingIntent)
-                                .setReplyAction(replyPendingIntent, testRemoteInput)
-                                .build()))
-                .build();
-
-        Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
-        n.visitUris(visitor);
-
-        verify(visitor, times(1)).accept(eq(readPendingIntentUri));
-        verify(visitor, times(1)).accept(eq(replyPendingIntentUri));
-        readPendingIntent.cancel();
-        replyPendingIntent.cancel();
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 863cda4..594d6f2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -34,6 +34,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -87,6 +88,7 @@
 import javax.annotation.Nullable;
 
 @RunWith(AndroidJUnit4.class)
+@EnableFlags(Flags.FLAG_VISIT_PERSON_URI)
 public class NotificationVisitUrisTest extends UiServiceTestCase {
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -153,10 +155,6 @@
                     .put(Notification.Action.Builder.class, "extend")
                     // Overwrites icon supplied to constructor.
                     .put(Notification.BubbleMetadata.Builder.class, "setIcon")
-                    // Overwrites intent supplied to constructor.
-                    .put(Notification.BubbleMetadata.Builder.class, "setIntent")
-                    // Overwrites intent supplied to constructor.
-                    .put(Notification.BubbleMetadata.Builder.class, "setDeleteIntent")
                     // Discards previously-added actions.
                     .put(RemoteViews.class, "mergeRemoteViews")
                     .build();
@@ -172,7 +170,6 @@
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
-        mSetFlagsRule.enableFlags(Flags.FLAG_VISIT_RISKY_URIS);
     }
 
     @After
@@ -700,14 +697,13 @@
             }
 
             if (clazz == Intent.class) {
-                return new Intent("action", generateUri(where.plus(Intent.class)));
+                return new Intent("action");
             }
 
             if (clazz == PendingIntent.class) {
-                // PendingIntent can have an Intent with a Uri.
-                Uri intentUri = generateUri(where.plus(PendingIntent.class));
-                return PendingIntent.getActivity(mContext, 0,
-                        new Intent("action", intentUri),
+                // PendingIntent can have an Intent with a Uri but those are inaccessible and
+                // not inspected.
+                return PendingIntent.getActivity(mContext, 0, new Intent("action"),
                         PendingIntent.FLAG_IMMUTABLE);
             }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java b/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
index 8cf2776..3e87f1f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java
@@ -125,7 +125,7 @@
                 processDuration < PRE_TASK_DELAY_MS);
 
         assertTrue("Target didn't call callback enough times.",
-                mFactory.waitForAllExpectedItemsProcessed(TIMEOUT_ALLOWANCE));
+                mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE));
         // Once before processing this item, once after that.
         assertEquals(2, mListener.mProbablyDoneResults.size());
         // The last one must be called with probably done being true.
diff --git a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
index c35242d..5f9a524 100644
--- a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
+++ b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
@@ -242,6 +242,16 @@
                 && Arrays.equals(mPolicyRuleFlags, that.mPolicyRuleFlags);
     }
 
+    @Override
+    public int hashCode() {
+        int result = Arrays.hashCode(mPolicyRules);
+        result = 31 * result + Arrays.hashCode(mPolicyRuleFlags);
+        for (int i = 0; i < mCarrierIds.length; i++) {
+            result = 31 * result + Arrays.hashCode(mCarrierIds[i]);
+        }
+        return result;
+    }
+
     private EuiccRulesAuthTable(Parcel source) {
         mPolicyRules = source.createIntArray();
         int len = mPolicyRules.length;
diff --git a/tests/TouchLatency/app/src/main/res/values/styles.xml b/tests/TouchLatency/app/src/main/res/values/styles.xml
index fa352cf..5058331 100644
--- a/tests/TouchLatency/app/src/main/res/values/styles.xml
+++ b/tests/TouchLatency/app/src/main/res/values/styles.xml
@@ -18,7 +18,7 @@
     <!-- Base application theme. -->
     <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
         <!-- Customize your theme here. -->
-        <item name="android:windowLayoutInDisplayCutoutMode">default</item>
+        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
     </style>
 
 </resources>
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
index d551953..c16d18b 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
@@ -23,6 +23,7 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -40,6 +41,11 @@
         this.mDataTypes = dataTypes;
     }
 
+    public DataCategory(String categoryName) {
+        this.mCategoryName = categoryName;
+        this.mDataTypes = new LinkedHashMap<String, DataType>();
+    }
+
     public String getCategoryName() {
         return mCategoryName;
     }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
index 90424fe..7244162 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
@@ -26,33 +26,8 @@
 import java.util.List;
 import java.util.Map;
 
-public class DataCategoryFactory implements AslMarshallableFactory<DataCategory> {
-    @Override
-    public DataCategory createFromHrElements(List<Element> elements) throws MalformedXmlException {
-        String categoryName = null;
-        Map<String, DataType> dataTypeMap = new LinkedHashMap<String, DataType>();
-        for (Element ele : elements) {
-            categoryName = XmlUtils.getStringAttr(ele, XmlUtils.HR_ATTR_DATA_CATEGORY, true);
-            String dataTypeName = XmlUtils.getStringAttr(ele, XmlUtils.HR_ATTR_DATA_TYPE, true);
-            if (!DataTypeConstants.getValidDataTypes().containsKey(categoryName)) {
-                throw new MalformedXmlException(
-                        String.format("Unrecognized data category %s", categoryName));
-            }
-            if (!DataTypeConstants.getValidDataTypes().get(categoryName).contains(dataTypeName)) {
-                throw new MalformedXmlException(
-                        String.format(
-                                "Unrecognized data type name %s for category %s",
-                                dataTypeName, categoryName));
-            }
-            dataTypeMap.put(
-                    dataTypeName, new DataTypeFactory().createFromHrElements(XmlUtils.listOf(ele)));
-        }
-
-        return new DataCategory(categoryName, dataTypeMap);
-    }
-
+public class DataCategoryFactory {
     /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
-    @Override
     public DataCategory createFromOdElements(List<Element> elements) throws MalformedXmlException {
         Element dataCategoryEle = XmlUtils.getSingleElement(elements);
         Map<String, DataType> dataTypeMap = new LinkedHashMap<String, DataType>();
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
index 4a0d759..ba0e3db 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
@@ -30,28 +30,17 @@
  * DataCategory}
  */
 public class DataLabels implements AslMarshallable {
-    private final Map<String, DataCategory> mDataAccessed;
     private final Map<String, DataCategory> mDataCollected;
     private final Map<String, DataCategory> mDataShared;
 
     public DataLabels(
-            Map<String, DataCategory> dataAccessed,
             Map<String, DataCategory> dataCollected,
             Map<String, DataCategory> dataShared) {
-        mDataAccessed = dataAccessed;
         mDataCollected = dataCollected;
         mDataShared = dataShared;
     }
 
     /**
-     * Returns the data accessed {@link Map} of {@link DataCategoryConstants} to {@link
-     * DataCategory}
-     */
-    public Map<String, DataCategory> getDataAccessed() {
-        return mDataAccessed;
-    }
-
-    /**
      * Returns the data collected {@link Map} of {@link DataCategoryConstants} to {@link
      * DataCategory}
      */
@@ -72,7 +61,6 @@
         Element dataLabelsEle =
                 XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DATA_LABELS);
 
-        maybeAppendDataUsages(doc, dataLabelsEle, mDataAccessed, XmlUtils.OD_NAME_DATA_ACCESSED);
         maybeAppendDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.OD_NAME_DATA_COLLECTED);
         maybeAppendDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.OD_NAME_DATA_SHARED);
 
@@ -83,9 +71,12 @@
     @Override
     public List<Element> toHrDomElements(Document doc) {
         Element dataLabelsEle = doc.createElement(XmlUtils.HR_TAG_DATA_LABELS);
-        maybeAppendHrDataUsages(doc, dataLabelsEle, mDataAccessed, XmlUtils.HR_TAG_DATA_ACCESSED);
-        maybeAppendHrDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.HR_TAG_DATA_COLLECTED);
-        maybeAppendHrDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.HR_TAG_DATA_SHARED);
+        maybeAppendHrDataUsages(
+                doc, dataLabelsEle, mDataCollected, XmlUtils.HR_TAG_DATA_COLLECTED, false);
+        maybeAppendHrDataUsages(
+                doc, dataLabelsEle, mDataCollected, XmlUtils.HR_TAG_DATA_COLLECTED_EPHEMERAL, true);
+        maybeAppendHrDataUsages(
+                doc, dataLabelsEle, mDataShared, XmlUtils.HR_TAG_DATA_SHARED, false);
         return XmlUtils.listOf(dataLabelsEle);
     }
 
@@ -115,7 +106,8 @@
             Document doc,
             Element dataLabelsEle,
             Map<String, DataCategory> dataCategoriesMap,
-            String dataUsageTypeName) {
+            String dataUsageTypeName,
+            boolean ephemeral) {
         if (dataCategoriesMap.isEmpty()) {
             return;
         }
@@ -123,10 +115,15 @@
             DataCategory dataCategory = dataCategoriesMap.get(dataCategoryName);
             for (String dataTypeName : dataCategory.getDataTypes().keySet()) {
                 DataType dataType = dataCategory.getDataTypes().get(dataTypeName);
-                // XmlUtils.appendChildren(dataLabelsEle, dataType.toHrDomElements(doc));
+                if (ephemeral
+                        != (dataType.getEphemeral() != null ? dataType.getEphemeral() : false)) {
+                    continue;
+                }
+
                 Element hrDataTypeEle = doc.createElement(dataUsageTypeName);
-                hrDataTypeEle.setAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY, dataCategoryName);
-                hrDataTypeEle.setAttribute(XmlUtils.HR_ATTR_DATA_TYPE, dataTypeName);
+                hrDataTypeEle.setAttribute(
+                        XmlUtils.HR_ATTR_DATA_TYPE,
+                        dataCategoryName + XmlUtils.DATA_TYPE_SEPARATOR + dataTypeName);
                 XmlUtils.maybeSetHrBoolAttr(
                         hrDataTypeEle,
                         XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL,
@@ -135,8 +132,6 @@
                         hrDataTypeEle,
                         XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL,
                         dataType.getIsSharingOptional());
-                XmlUtils.maybeSetHrBoolAttr(
-                        hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL, dataType.getEphemeral());
                 hrDataTypeEle.setAttribute(
                         XmlUtils.HR_ATTR_PURPOSES,
                         String.join(
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
index 5473e01..c4d8876 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
@@ -18,16 +18,15 @@
 
 import com.android.asllib.util.AslgenUtil;
 import com.android.asllib.util.DataCategoryConstants;
+import com.android.asllib.util.DataTypeConstants;
 import com.android.asllib.util.MalformedXmlException;
 import com.android.asllib.util.XmlUtils;
 
 import org.w3c.dom.Element;
 
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
 
@@ -39,13 +38,46 @@
             AslgenUtil.logI("Found no DataLabels in hr format.");
             return null;
         }
-        Map<String, DataCategory> dataAccessed =
-                getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_ACCESSED);
         Map<String, DataCategory> dataCollected =
-                getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED);
+                getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED, false);
+        Map<String, DataCategory> dataCollectedEphemeral =
+                getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED_EPHEMERAL, true);
         Map<String, DataCategory> dataShared =
-                getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_SHARED);
-        DataLabels dataLabels = new DataLabels(dataAccessed, dataCollected, dataShared);
+                getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_SHARED, null);
+
+        for (String dataCollectedEphemeralDataCategoryKey : dataCollectedEphemeral.keySet()) {
+            DataCategory dataCategoryEphemeral =
+                    dataCollectedEphemeral.get(dataCollectedEphemeralDataCategoryKey);
+            for (String dataCollectedEphemeralDataTypeKey :
+                    dataCategoryEphemeral.getDataTypes().keySet()) {
+                if (dataCollected.containsKey(dataCollectedEphemeralDataCategoryKey)
+                        && dataCollected
+                                .get(dataCollectedEphemeralDataCategoryKey)
+                                .getDataTypes()
+                                .containsKey(dataCollectedEphemeralDataTypeKey)) {
+                    throw new MalformedXmlException(
+                            String.format(
+                                    "Duplicate entries in data-collected and"
+                                            + " data-collected-ephemeral: %s %s",
+                                    dataCollectedEphemeralDataCategoryKey,
+                                    dataCollectedEphemeralDataTypeKey));
+                }
+
+                if (!dataCollected.containsKey(dataCollectedEphemeralDataCategoryKey)) {
+                    dataCollected.put(
+                            dataCollectedEphemeralDataCategoryKey,
+                            new DataCategory(dataCollectedEphemeralDataCategoryKey));
+                }
+                DataType dataTypeEphemeral =
+                        dataCategoryEphemeral.getDataTypes().get(dataCollectedEphemeralDataTypeKey);
+                dataCollected
+                        .get(dataCollectedEphemeralDataCategoryKey)
+                        .getDataTypes()
+                        .put(dataCollectedEphemeralDataTypeKey, dataTypeEphemeral);
+            }
+        }
+        DataLabels dataLabels = new DataLabels(dataCollected, dataShared);
+
         validateIsXOptional(dataLabels);
         return dataLabels;
     }
@@ -58,13 +90,11 @@
             AslgenUtil.logI("Found no DataLabels in od format.");
             return null;
         }
-        Map<String, DataCategory> dataAccessed =
-                getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_ACCESSED);
         Map<String, DataCategory> dataCollected =
                 getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_COLLECTED);
         Map<String, DataCategory> dataShared =
                 getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_SHARED);
-        DataLabels dataLabels = new DataLabels(dataAccessed, dataCollected, dataShared);
+        DataLabels dataLabels = new DataLabels(dataCollected, dataShared);
         validateIsXOptional(dataLabels);
         return dataLabels;
     }
@@ -88,56 +118,56 @@
     }
 
     private static Map<String, DataCategory> getDataCategoriesWithTag(
-            Element dataLabelsEle, String dataCategoryUsageTypeTag) throws MalformedXmlException {
+            Element dataLabelsEle, String dataCategoryUsageTypeTag, Boolean ephemeral)
+            throws MalformedXmlException {
         List<Element> dataUsedElements =
                 XmlUtils.getChildrenByTagName(dataLabelsEle, dataCategoryUsageTypeTag);
         Map<String, DataCategory> dataCategoryMap = new LinkedHashMap<String, DataCategory>();
 
-        Set<String> dataCategoryNames = new HashSet<String>();
         for (int i = 0; i < dataUsedElements.size(); i++) {
             Element dataUsedEle = dataUsedElements.get(i);
-            String dataCategoryName = dataUsedEle.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY);
+            String dataCategoryAndTypeCombinedStr =
+                    dataUsedEle.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE);
+            String[] strs = dataCategoryAndTypeCombinedStr.split(XmlUtils.DATA_TYPE_SEPARATOR);
+            if (strs.length != 2) {
+                throw new MalformedXmlException(
+                        String.format(
+                                "Could not parse human-readable data type string (expecting"
+                                        + " substring of _data_type_): %s",
+                                dataCategoryAndTypeCombinedStr));
+            }
+            String dataCategoryName = strs[0];
+            String dataTypeName = strs[1];
+
             if (!DataCategoryConstants.getValidDataCategories().contains(dataCategoryName)) {
                 throw new MalformedXmlException(
                         String.format("Unrecognized category name: %s", dataCategoryName));
             }
-            dataCategoryNames.add(dataCategoryName);
+            if (!DataTypeConstants.getValidDataTypes()
+                    .get(dataCategoryName)
+                    .contains(dataTypeName)) {
+                throw new MalformedXmlException(
+                        String.format(
+                                "Unrecognized data type name %s for category %s",
+                                dataTypeName, dataCategoryName));
+            }
+
+            if (!dataCategoryMap.containsKey(dataCategoryName)) {
+                dataCategoryMap.put(dataCategoryName, new DataCategory(dataCategoryName));
+            }
+            dataCategoryMap
+                    .get(dataCategoryName)
+                    .getDataTypes()
+                    .put(
+                            dataTypeName,
+                            new DataTypeFactory().createFromHrElements(dataUsedEle, ephemeral));
         }
-        for (String dataCategoryName : dataCategoryNames) {
-            var dataCategoryElements =
-                    dataUsedElements.stream()
-                            .filter(
-                                    ele ->
-                                            ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY)
-                                                    .equals(dataCategoryName))
-                            .toList();
-            DataCategory dataCategory =
-                    new DataCategoryFactory().createFromHrElements(dataCategoryElements);
-            dataCategoryMap.put(dataCategoryName, dataCategory);
-        }
+
         return dataCategoryMap;
     }
 
     private void validateIsXOptional(DataLabels dataLabels) throws MalformedXmlException {
         // Validate booleans such as isCollectionOptional, isSharingOptional.
-        for (DataCategory dataCategory : dataLabels.getDataAccessed().values()) {
-            for (DataType dataType : dataCategory.getDataTypes().values()) {
-                if (dataType.getIsSharingOptional() != null) {
-                    throw new MalformedXmlException(
-                            String.format(
-                                    "isSharingOptional was unexpectedly defined on a DataType"
-                                            + " belonging to data accessed: %s",
-                                    dataType.getDataTypeName()));
-                }
-                if (dataType.getIsCollectionOptional() != null) {
-                    throw new MalformedXmlException(
-                            String.format(
-                                    "isCollectionOptional was unexpectedly defined on a DataType"
-                                            + " belonging to data accessed: %s",
-                                    dataType.getDataTypeName()));
-                }
-            }
-        }
         for (DataCategory dataCategory : dataLabels.getDataCollected().values()) {
             for (DataType dataType : dataCategory.getDataTypes().values()) {
                 if (dataType.getIsSharingOptional() != null) {
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
index 488c259..a5559d8 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
@@ -25,12 +25,22 @@
 import java.util.List;
 import java.util.stream.Collectors;
 
-public class DataTypeFactory implements AslMarshallableFactory<DataType> {
+public class DataTypeFactory {
     /** Creates a {@link DataType} from the human-readable DOM element. */
-    @Override
-    public DataType createFromHrElements(List<Element> elements) throws MalformedXmlException {
-        Element hrDataTypeEle = XmlUtils.getSingleElement(elements);
-        String dataTypeName = hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE);
+    public DataType createFromHrElements(Element hrDataTypeEle, Boolean ephemeral)
+            throws MalformedXmlException {
+        String dataCategoryAndTypeCombinedStr =
+                hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE);
+        String[] strs = dataCategoryAndTypeCombinedStr.split(XmlUtils.DATA_TYPE_SEPARATOR);
+        if (strs.length != 2) {
+            throw new MalformedXmlException(
+                    String.format(
+                            "Could not parse human-readable data type string (expecting substring"
+                                    + " of _data_type_): %s",
+                            dataCategoryAndTypeCombinedStr));
+        }
+        String dataTypeName = strs[1];
+
         List<DataType.Purpose> purposes =
                 XmlUtils.getPipelineSplitAttr(hrDataTypeEle, XmlUtils.HR_ATTR_PURPOSES, true)
                         .stream()
@@ -47,13 +57,13 @@
                 XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL, false);
         Boolean isSharingOptional =
                 XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL, false);
-        Boolean ephemeral = XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL, false);
+        // Boolean ephemeral = XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL,
+        // false);
         return new DataType(
                 dataTypeName, purposes, isCollectionOptional, isSharingOptional, ephemeral);
     }
 
     /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
-    @Override
     public DataType createFromOdElements(List<Element> elements) throws MalformedXmlException {
         Element odDataTypeEle = XmlUtils.getSingleElement(elements);
         String dataTypeName = odDataTypeEle.getAttribute(XmlUtils.OD_ATTR_NAME);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
index 854c0d0..242e7be 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
@@ -26,15 +26,10 @@
 /** Safety Label representation containing zero or more {@link DataCategory} for data shared */
 public class SystemAppSafetyLabel implements AslMarshallable {
 
-    private final String mUrl;
+    private final Boolean mDeclaration;
 
-    public SystemAppSafetyLabel(String url) {
-        this.mUrl = url;
-    }
-
-    /** Returns the system app safety label URL. */
-    public String getUrl() {
-        return mUrl;
+    public SystemAppSafetyLabel(Boolean d) {
+        this.mDeclaration = d;
     }
 
     /** Creates an on-device DOM element from the {@link SystemAppSafetyLabel}. */
@@ -43,7 +38,7 @@
         Element systemAppSafetyLabelEle =
                 XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL);
         systemAppSafetyLabelEle.appendChild(
-                XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl));
+                XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_DECLARATION, mDeclaration));
         return XmlUtils.listOf(systemAppSafetyLabelEle);
     }
 
@@ -52,7 +47,8 @@
     public List<Element> toHrDomElements(Document doc) {
         Element systemAppSafetyLabelEle =
                 doc.createElement(XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL);
-        systemAppSafetyLabelEle.setAttribute(XmlUtils.HR_ATTR_URL, mUrl);
+        XmlUtils.maybeSetHrBoolAttr(
+                systemAppSafetyLabelEle, XmlUtils.HR_ATTR_DECLARATION, mDeclaration);
         return XmlUtils.listOf(systemAppSafetyLabelEle);
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
index c8e22b6..7f4aa7a 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
@@ -36,8 +36,9 @@
             return null;
         }
 
-        String url = XmlUtils.getStringAttr(systemAppSafetyLabelEle, XmlUtils.HR_ATTR_URL, true);
-        return new SystemAppSafetyLabel(url);
+        Boolean declaration =
+                XmlUtils.getBoolAttr(systemAppSafetyLabelEle, XmlUtils.HR_ATTR_DECLARATION, true);
+        return new SystemAppSafetyLabel(declaration);
     }
 
     /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
@@ -49,7 +50,8 @@
             AslgenUtil.logI("No SystemAppSafetyLabel found in od format.");
             return null;
         }
-        String url = XmlUtils.getOdStringEle(systemAppSafetyLabelEle, XmlUtils.OD_NAME_URL, true);
-        return new SystemAppSafetyLabel(url);
+        Boolean declaration =
+                XmlUtils.getOdBoolEle(systemAppSafetyLabelEle, XmlUtils.OD_NAME_DECLARATION, true);
+        return new SystemAppSafetyLabel(declaration);
     }
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
index 1d54ead..97cbc39 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
@@ -27,6 +27,8 @@
 import java.util.List;
 
 public class XmlUtils {
+    public static final String DATA_TYPE_SEPARATOR = "_data_type_";
+
     public static final String HR_TAG_APP_METADATA_BUNDLES = "app-metadata-bundles";
     public static final String HR_TAG_SYSTEM_APP_SAFETY_LABEL = "system-app-safety-label";
     public static final String HR_TAG_SAFETY_LABELS = "safety-labels";
@@ -38,6 +40,7 @@
     public static final String HR_TAG_THIRD_PARTY_VERIFICATION = "third-party-verification";
     public static final String HR_TAG_DATA_ACCESSED = "data-accessed";
     public static final String HR_TAG_DATA_COLLECTED = "data-collected";
+    public static final String HR_TAG_DATA_COLLECTED_EPHEMERAL = "data-collected-ephemeral";
     public static final String HR_TAG_DATA_SHARED = "data-shared";
     public static final String HR_ATTR_NAME = "name";
     public static final String HR_ATTR_EMAIL = "email";
@@ -52,10 +55,11 @@
     public static final String HR_ATTR_IS_SHARING_OPTIONAL = "isSharingOptional";
     public static final String HR_ATTR_IS_DATA_DELETABLE = "isDataDeletable";
     public static final String HR_ATTR_IS_DATA_ENCRYPTED = "isDataEncrypted";
-    public static final String HR_ATTR_EPHEMERAL = "ephemeral";
+    // public static final String HR_ATTR_EPHEMERAL = "ephemeral";
     public static final String HR_ATTR_PURPOSES = "purposes";
     public static final String HR_ATTR_VERSION = "version";
     public static final String HR_ATTR_URL = "url";
+    public static final String HR_ATTR_DECLARATION = "declaration";
     public static final String HR_ATTR_TITLE = "title";
     public static final String HR_ATTR_DESCRIPTION = "description";
     public static final String HR_ATTR_CONTAINS_ADS = "containsAds";
@@ -103,6 +107,7 @@
     public static final String OD_NAME_CATEGORY = "category";
     public static final String OD_NAME_VERSION = "version";
     public static final String OD_NAME_URL = "url";
+    public static final String OD_NAME_DECLARATION = "declaration";
     public static final String OD_NAME_SYSTEM_APP_SAFETY_LABEL = "system_app_safety_label";
     public static final String OD_NAME_SECURITY_LABELS = "security_labels";
     public static final String OD_NAME_THIRD_PARTY_VERIFICATION = "third_party_verification";
@@ -299,12 +304,13 @@
                         .toList();
         if (boolEles.size() > 1) {
             throw new MalformedXmlException(
-                    String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
+                    String.format(
+                            "Found more than one boolean %s in %s.", nameName, ele.getTagName()));
         }
         if (boolEles.isEmpty()) {
             if (required) {
                 throw new MalformedXmlException(
-                        String.format("Found no %s in %s.", nameName, ele.getTagName()));
+                        String.format("Found no boolean %s in %s.", nameName, ele.getTagName()));
             }
             return null;
         }
@@ -329,12 +335,13 @@
                         .toList();
         if (longEles.size() > 1) {
             throw new MalformedXmlException(
-                    String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
+                    String.format(
+                            "Found more than one long %s in %s.", nameName, ele.getTagName()));
         }
         if (longEles.isEmpty()) {
             if (required) {
                 throw new MalformedXmlException(
-                        String.format("Found no %s in %s.", nameName, ele.getTagName()));
+                        String.format("Found no long %s in %s.", nameName, ele.getTagName()));
             }
             return null;
         }
@@ -359,12 +366,13 @@
                         .toList();
         if (eles.size() > 1) {
             throw new MalformedXmlException(
-                    String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
+                    String.format(
+                            "Found more than one string %s in %s.", nameName, ele.getTagName()));
         }
         if (eles.isEmpty()) {
             if (required) {
                 throw new MalformedXmlException(
-                        String.format("Found no %s in %s.", nameName, ele.getTagName()));
+                        String.format("Found no string %s in %s.", nameName, ele.getTagName()));
             }
             return null;
         }
@@ -386,12 +394,13 @@
                         .toList();
         if (eles.size() > 1) {
             throw new MalformedXmlException(
-                    String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
+                    String.format(
+                            "Found more than one pbundle %s in %s.", nameName, ele.getTagName()));
         }
         if (eles.isEmpty()) {
             if (required) {
                 throw new MalformedXmlException(
-                        String.format("Found no %s in %s.", nameName, ele.getTagName()));
+                        String.format("Found no pbundle %s in %s.", nameName, ele.getTagName()));
             }
             return null;
         }
@@ -456,12 +465,15 @@
                         .toList();
         if (arrayEles.size() > 1) {
             throw new MalformedXmlException(
-                    String.format("Found more than one %s in %s.", nameName, ele.getTagName()));
+                    String.format(
+                            "Found more than one string array %s in %s.",
+                            nameName, ele.getTagName()));
         }
         if (arrayEles.isEmpty()) {
             if (required) {
                 throw new MalformedXmlException(
-                        String.format("Found no %s in %s.", nameName, ele.getTagName()));
+                        String.format(
+                                "Found no string array %s in %s.", nameName, ele.getTagName()));
             }
             return null;
         }
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
index f156484..dbeeb49 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
@@ -18,7 +18,6 @@
 
 import com.android.asllib.marshallable.AndroidSafetyLabelTest;
 import com.android.asllib.marshallable.AppInfoTest;
-import com.android.asllib.marshallable.DataCategoryTest;
 import com.android.asllib.marshallable.DataLabelsTest;
 import com.android.asllib.marshallable.DataTypeEqualityTest;
 import com.android.asllib.marshallable.DeveloperInfoTest;
@@ -36,7 +35,7 @@
     AslgenTests.class,
     AndroidSafetyLabelTest.class,
     AppInfoTest.class,
-    DataCategoryTest.class,
+    // DataCategoryTest.class,
     DataLabelsTest.class,
     DataTypeEqualityTest.class,
     DeveloperInfoTest.class,
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
index d2e0fc3..5d1d45a 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
@@ -34,8 +34,7 @@
 @RunWith(JUnit4.class)
 public class AslgenTests {
     private static final String VALID_MAPPINGS_PATH = "com/android/asllib/validmappings";
-    private static final List<String> VALID_MAPPINGS_SUBDIRS =
-            List.of("location", "contacts", "general");
+    private static final List<String> VALID_MAPPINGS_SUBDIRS = List.of("general");
     private static final String HR_XML_FILENAME = "hr.xml";
     private static final String OD_XML_FILENAME = "od.xml";
 
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java
deleted file mode 100644
index ebb3186..0000000
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.asllib.marshallable;
-
-import com.android.asllib.testutils.TestUtils;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class DataCategoryTest {
-    private static final String DATA_CATEGORY_HR_PATH = "com/android/asllib/datacategory/hr";
-    private static final String DATA_CATEGORY_OD_PATH = "com/android/asllib/datacategory/od";
-
-    private static final String VALID_PERSONAL_FILE_NAME = "data-category-personal.xml";
-    private static final String VALID_PARTIAL_PERSONAL_FILE_NAME =
-            "data-category-personal-partial.xml";
-    private static final String VALID_FINANCIAL_FILE_NAME = "data-category-financial.xml";
-    private static final String VALID_LOCATION_FILE_NAME = "data-category-location.xml";
-    private static final String VALID_EMAIL_TEXT_MESSAGE_FILE_NAME =
-            "data-category-email-text-message.xml";
-    private static final String VALID_PHOTO_VIDEO_FILE_NAME = "data-category-photo-video.xml";
-    private static final String VALID_AUDIO_FILE_NAME = "data-category-audio.xml";
-    private static final String VALID_STORAGE_FILE_NAME = "data-category-storage.xml";
-    private static final String VALID_HEALTH_FITNESS_FILE_NAME = "data-category-health-fitness.xml";
-    private static final String VALID_CONTACTS_FILE_NAME = "data-category-contacts.xml";
-    private static final String VALID_CALENDAR_FILE_NAME = "data-category-calendar.xml";
-    private static final String VALID_IDENTIFIERS_FILE_NAME = "data-category-identifiers.xml";
-    private static final String VALID_APP_PERFORMANCE_FILE_NAME =
-            "data-category-app-performance.xml";
-    private static final String VALID_ACTIONS_IN_APP_FILE_NAME = "data-category-actions-in-app.xml";
-    private static final String VALID_SEARCH_AND_BROWSING_FILE_NAME =
-            "data-category-search-and-browsing.xml";
-
-    private static final String EMPTY_PURPOSE_PERSONAL_FILE_NAME =
-            "data-category-personal-empty-purpose.xml";
-    private static final String MISSING_PURPOSE_PERSONAL_FILE_NAME =
-            "data-category-personal-missing-purpose.xml";
-    private static final String UNRECOGNIZED_TYPE_PERSONAL_FILE_NAME =
-            "data-category-personal-unrecognized-type.xml";
-    private static final String UNRECOGNIZED_CATEGORY_FILE_NAME = "data-category-unrecognized.xml";
-
-    /** Logic for setting up tests (empty if not yet needed). */
-    public static void main(String[] params) throws Exception {}
-
-    @Before
-    public void setUp() throws Exception {
-        System.out.println("set up.");
-    }
-
-    /** Test for data category personal. */
-    @Test
-    public void testDataCategoryPersonal() throws Exception {
-        System.out.println("starting testDataCategoryPersonal.");
-        testHrToOdDataCategory(VALID_PERSONAL_FILE_NAME);
-    }
-
-    /** Test for data category financial. */
-    @Test
-    public void testDataCategoryFinancial() throws Exception {
-        System.out.println("starting testDataCategoryFinancial.");
-        testHrToOdDataCategory(VALID_FINANCIAL_FILE_NAME);
-    }
-
-    /** Test for data category location. */
-    @Test
-    public void testDataCategoryLocation() throws Exception {
-        System.out.println("starting testDataCategoryLocation.");
-        testHrToOdDataCategory(VALID_LOCATION_FILE_NAME);
-    }
-
-    /** Test for data category email text message. */
-    @Test
-    public void testDataCategoryEmailTextMessage() throws Exception {
-        System.out.println("starting testDataCategoryEmailTextMessage.");
-        testHrToOdDataCategory(VALID_EMAIL_TEXT_MESSAGE_FILE_NAME);
-    }
-
-    /** Test for data category photo video. */
-    @Test
-    public void testDataCategoryPhotoVideo() throws Exception {
-        System.out.println("starting testDataCategoryPhotoVideo.");
-        testHrToOdDataCategory(VALID_PHOTO_VIDEO_FILE_NAME);
-    }
-
-    /** Test for data category audio. */
-    @Test
-    public void testDataCategoryAudio() throws Exception {
-        System.out.println("starting testDataCategoryAudio.");
-        testHrToOdDataCategory(VALID_AUDIO_FILE_NAME);
-    }
-
-    /** Test for data category storage. */
-    @Test
-    public void testDataCategoryStorage() throws Exception {
-        System.out.println("starting testDataCategoryStorage.");
-        testHrToOdDataCategory(VALID_STORAGE_FILE_NAME);
-    }
-
-    /** Test for data category health fitness. */
-    @Test
-    public void testDataCategoryHealthFitness() throws Exception {
-        System.out.println("starting testDataCategoryHealthFitness.");
-        testHrToOdDataCategory(VALID_HEALTH_FITNESS_FILE_NAME);
-    }
-
-    /** Test for data category contacts. */
-    @Test
-    public void testDataCategoryContacts() throws Exception {
-        System.out.println("starting testDataCategoryContacts.");
-        testHrToOdDataCategory(VALID_CONTACTS_FILE_NAME);
-    }
-
-    /** Test for data category calendar. */
-    @Test
-    public void testDataCategoryCalendar() throws Exception {
-        System.out.println("starting testDataCategoryCalendar.");
-        testHrToOdDataCategory(VALID_CALENDAR_FILE_NAME);
-    }
-
-    /** Test for data category identifiers. */
-    @Test
-    public void testDataCategoryIdentifiers() throws Exception {
-        System.out.println("starting testDataCategoryIdentifiers.");
-        testHrToOdDataCategory(VALID_IDENTIFIERS_FILE_NAME);
-    }
-
-    /** Test for data category app performance. */
-    @Test
-    public void testDataCategoryAppPerformance() throws Exception {
-        System.out.println("starting testDataCategoryAppPerformance.");
-        testHrToOdDataCategory(VALID_APP_PERFORMANCE_FILE_NAME);
-    }
-
-    /** Test for data category actions in app. */
-    @Test
-    public void testDataCategoryActionsInApp() throws Exception {
-        System.out.println("starting testDataCategoryActionsInApp.");
-        testHrToOdDataCategory(VALID_ACTIONS_IN_APP_FILE_NAME);
-    }
-
-    /** Test for data category search and browsing. */
-    @Test
-    public void testDataCategorySearchAndBrowsing() throws Exception {
-        System.out.println("starting testDataCategorySearchAndBrowsing.");
-        testHrToOdDataCategory(VALID_SEARCH_AND_BROWSING_FILE_NAME);
-    }
-
-    /** Test for data category search and browsing. */
-    @Test
-    public void testMissingOptionalsAllowed() throws Exception {
-        System.out.println("starting testMissingOptionalsAllowed.");
-        testHrToOdDataCategory(VALID_PARTIAL_PERSONAL_FILE_NAME);
-    }
-
-    /** Test for empty purposes. */
-    @Test
-    public void testEmptyPurposesNotAllowed() throws Exception {
-        System.out.println("starting testEmptyPurposesNotAllowed.");
-        hrToOdExpectException(EMPTY_PURPOSE_PERSONAL_FILE_NAME);
-    }
-
-    /** Test for missing purposes. */
-    @Test
-    public void testMissingPurposesNotAllowed() throws Exception {
-        System.out.println("starting testMissingPurposesNotAllowed.");
-        hrToOdExpectException(MISSING_PURPOSE_PERSONAL_FILE_NAME);
-    }
-
-    /** Test for unrecognized type. */
-    @Test
-    public void testUnrecognizedTypeNotAllowed() throws Exception {
-        System.out.println("starting testUnrecognizedTypeNotAllowed.");
-        hrToOdExpectException(UNRECOGNIZED_TYPE_PERSONAL_FILE_NAME);
-    }
-
-    /** Test for unrecognized category. */
-    @Test
-    public void testUnrecognizedCategoryNotAllowed() throws Exception {
-        System.out.println("starting testUnrecognizedCategoryNotAllowed.");
-        hrToOdExpectException(UNRECOGNIZED_CATEGORY_FILE_NAME);
-    }
-
-    private void hrToOdExpectException(String fileName) {
-        TestUtils.hrToOdExpectException(new DataCategoryFactory(), DATA_CATEGORY_HR_PATH, fileName);
-    }
-
-    private void testHrToOdDataCategory(String fileName) throws Exception {
-        TestUtils.testHrToOd(
-                TestUtils.document(),
-                new DataCategoryFactory(),
-                DATA_CATEGORY_HR_PATH,
-                DATA_CATEGORY_OD_PATH,
-                fileName);
-    }
-}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
index 2661726..ff43741 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
@@ -34,6 +34,10 @@
             "data-labels-accessed-invalid-bool.xml";
     private static final String COLLECTED_VALID_BOOL_FILE_NAME =
             "data-labels-collected-valid-bool.xml";
+    private static final String COLLECTED_EPHEMERAL_FILE_NAME =
+            "data-labels-collected-ephemeral.xml";
+    private static final String COLLECTED_EPHEMERAL_COLLISION_FILE_NAME =
+            "data-labels-collected-ephemeral-collision.xml";
     private static final String COLLECTED_INVALID_BOOL_FILE_NAME =
             "data-labels-collected-invalid-bool.xml";
     private static final String SHARED_VALID_BOOL_FILE_NAME = "data-labels-shared-valid-bool.xml";
@@ -69,21 +73,6 @@
         System.out.println("set up.");
     }
 
-    /** Test for data labels accessed valid bool. */
-    @Test
-    public void testDataLabelsAccessedValidBool() throws Exception {
-        System.out.println("starting testDataLabelsAccessedValidBool.");
-        testHrToOdDataLabels(ACCESSED_VALID_BOOL_FILE_NAME);
-        testOdToHrDataLabels(ACCESSED_VALID_BOOL_FILE_NAME);
-    }
-
-    /** Test for data labels accessed invalid bool. */
-    @Test
-    public void testDataLabelsAccessedInvalidBool() throws Exception {
-        System.out.println("starting testDataLabelsAccessedInvalidBool.");
-        hrToOdExpectException(ACCESSED_INVALID_BOOL_FILE_NAME);
-    }
-
     /** Test for data labels collected valid bool. */
     @Test
     public void testDataLabelsCollectedValidBool() throws Exception {
@@ -92,6 +81,21 @@
         testOdToHrDataLabels(COLLECTED_VALID_BOOL_FILE_NAME);
     }
 
+    /** Test for data labels collected ephemeral. */
+    @Test
+    public void testDataLabelsCollectedEphemeral() throws Exception {
+        System.out.println("starting testDataLabelsCollectedEphemeral.");
+        testHrToOdDataLabels(COLLECTED_EPHEMERAL_FILE_NAME);
+        testOdToHrDataLabels(COLLECTED_EPHEMERAL_FILE_NAME);
+    }
+
+    /** Test for data labels ephemeral collision. */
+    @Test
+    public void testDataLabelsCollectedEphemeralCollision() throws Exception {
+        System.out.println("starting testDataLabelsCollectedEphemeralCollision.");
+        hrToOdExpectException(COLLECTED_EPHEMERAL_COLLISION_FILE_NAME);
+    }
+
     /** Test for data labels collected invalid bool. */
     @Test
     public void testDataLabelsCollectedInvalidBool() throws Exception {
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
index 33c2764..87d3e44 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
@@ -31,7 +31,7 @@
             "com/android/asllib/systemappsafetylabel/od";
 
     private static final String VALID_FILE_NAME = "valid.xml";
-    private static final String MISSING_URL_FILE_NAME = "missing-url.xml";
+    private static final String MISSING_BOOL_FILE_NAME = "missing-bool.xml";
 
     /** Logic for setting up tests (empty if not yet needed). */
     public static void main(String[] params) throws Exception {}
@@ -49,12 +49,12 @@
         testOdToHrSystemAppSafetyLabel(VALID_FILE_NAME);
     }
 
-    /** Tests missing url. */
+    /** Tests missing bool. */
     @Test
-    public void testMissingUrl() throws Exception {
-        System.out.println("starting testMissingUrl.");
-        hrToOdExpectException(MISSING_URL_FILE_NAME);
-        odToHrExpectException(MISSING_URL_FILE_NAME);
+    public void testMissingBool() throws Exception {
+        System.out.println("starting testMissingBool.");
+        hrToOdExpectException(MISSING_BOOL_FILE_NAME);
+        odToHrExpectException(MISSING_BOOL_FILE_NAME);
     }
 
     private void hrToOdExpectException(String fileName) {
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml
index 7bcde45..afb0486 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml
@@ -1,4 +1,4 @@
 <app-metadata-bundles version="123456">
-<system-app-safety-label url="www.example.com">
+<system-app-safety-label declaration="true">
 </system-app-safety-label>
 </app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml
index ef0f549..e8640c4 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml
@@ -1,6 +1,6 @@
 <bundle>
     <long name="version" value="123456"/>
     <pbundle_as_map name="system_app_safety_label">
-        <string name="url" value="www.example.com"/>
+        <boolean name="declaration" value="true"/>
     </pbundle_as_map>
 </bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml
index 68e191e..680e01a 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml
@@ -1,17 +1,12 @@
 <data-labels>
-    <data-shared dataCategory="actions_in_app"
-        dataType="user_interaction"
+    <data-shared dataType="actions_in_app_data_type_user_interaction"
         purposes="analytics" />
-    <data-shared dataCategory="actions_in_app"
-        dataType="in_app_search_history"
+    <data-shared dataType="actions_in_app_data_type_in_app_search_history"
         purposes="analytics" />
-    <data-shared dataCategory="actions_in_app"
-        dataType="installed_apps"
+    <data-shared dataType="actions_in_app_data_type_installed_apps"
         purposes="analytics" />
-    <data-shared dataCategory="actions_in_app"
-        dataType="user_generated_content"
+    <data-shared dataType="actions_in_app_data_type_user_generated_content"
         purposes="analytics" />
-    <data-shared dataCategory="actions_in_app"
-        dataType="other"
+    <data-shared dataType="actions_in_app_data_type_other"
         purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml
index a6bd17d..db114bf 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml
@@ -1,11 +1,8 @@
 <data-labels>
-    <data-shared dataCategory="app_performance"
-        dataType="crash_logs"
+    <data-shared dataType="app_performance_data_type_crash_logs"
         purposes="analytics" />
-    <data-shared dataCategory="app_performance"
-        dataType="performance_diagnostics"
+    <data-shared dataType="app_performance_data_type_performance_diagnostics"
         purposes="analytics" />
-    <data-shared dataCategory="app_performance"
-        dataType="other"
+    <data-shared dataType="app_performance_data_type_other"
         purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml
index 6274604..cf273f4 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml
@@ -1,11 +1,8 @@
 <data-labels>
-    <data-shared dataCategory="audio"
-        dataType="sound_recordings"
+    <data-shared dataType="audio_data_type_sound_recordings"
         purposes="analytics" />
-    <data-shared dataCategory="audio"
-        dataType="music_files"
+    <data-shared dataType="audio_data_type_music_files"
         purposes="analytics" />
-    <data-shared dataCategory="audio"
-        dataType="other"
+    <data-shared dataType="audio_data_type_other"
         purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml
index f7201f6..16f9d9b6 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml
@@ -1,5 +1,4 @@
 <data-labels>
-    <data-shared dataCategory="calendar"
-        dataType="calendar"
+    <data-shared dataType="calendar_data_type_calendar"
         purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml
index e8d40be..6d7a4e8 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml
@@ -1,5 +1,4 @@
 <data-labels>
-    <data-shared dataCategory="contacts"
-        dataType="contacts"
+    <data-shared dataType="contacts_data_type_contacts"
         purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml
index 69e9b87..7a9e978 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml
@@ -1,11 +1,8 @@
 <data-labels>
-    <data-shared dataCategory="email_text_message"
-        dataType="emails"
+    <data-shared dataType="email_text_message_data_type_emails"
         purposes="analytics" />
-    <data-shared dataCategory="email_text_message"
-        dataType="text_messages"
+    <data-shared dataType="email_text_message_data_type_text_messages"
         purposes="analytics" />
-    <data-shared dataCategory="email_text_message"
-        dataType="other"
+    <data-shared dataType="email_text_message_data_type_other"
         purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml
index fdd8456..24385b6 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml
@@ -1,14 +1,10 @@
 <data-labels>
-    <data-shared dataCategory="financial"
-        dataType="card_bank_account"
+    <data-shared dataType="financial_data_type_card_bank_account"
         purposes="analytics" />
-    <data-shared dataCategory="financial"
-    dataType="purchase_history"
+    <data-shared dataType="financial_data_type_purchase_history"
     purposes="analytics" />
-    <data-shared dataCategory="financial"
-        dataType="credit_score"
+    <data-shared dataType="financial_data_type_credit_score"
         purposes="analytics" />
-    <data-shared dataCategory="financial"
-        dataType="other"
+    <data-shared dataType="financial_data_type_other"
         purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml
index bac58e6..faf30b0 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml
@@ -1,8 +1,6 @@
 <data-labels>
-    <data-shared dataCategory="health_fitness"
-        dataType="health"
+    <data-shared dataType="health_fitness_data_type_health"
         purposes="analytics" />
-    <data-shared dataCategory="health_fitness"
-        dataType="fitness"
+    <data-shared dataType="health_fitness_data_type_fitness"
         purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml
index ee45f26..5101906 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml
@@ -1,5 +1,4 @@
 <data-labels>
-    <data-shared dataCategory="identifiers"
-        dataType="other"
+    <data-shared dataType="identifiers_data_type_other"
         purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml
index e8e5911..72cda7e 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml
@@ -1,8 +1,6 @@
 <data-labels>
-    <data-shared dataCategory="location"
-        dataType="approx_location"
+    <data-shared dataType="location_data_type_approx_location"
         purposes="analytics" />
-    <data-shared dataCategory="location"
-        dataType="precise_location"
+    <data-shared dataType="location_data_type_precise_location"
         purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml
index 0b220f4..2558681 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml
@@ -1,5 +1,4 @@
 <data-labels>
-    <data-shared dataCategory="personal"
-    dataType="email_address"
+    <data-shared dataType="personal_data_type_email_address"
     purposes="" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml
index ac221f2..c5a5475 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml
@@ -1,4 +1,3 @@
 <data-labels>
-    <data-shared dataCategory="personal"
-    dataType="email_address" />
+    <data-shared dataType="personal_data_type_email_address" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml
index 11b7368..6ccf336 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml
@@ -1,8 +1,6 @@
 <data-labels>
-    <data-shared dataCategory="personal"
-        dataType="name"
+    <data-shared dataType="personal_data_type_name"
         purposes="analytics|developer_communications" />
-    <data-shared dataCategory="personal"
-    dataType="email_address"
+    <data-shared dataType="personal_data_type_email_address"
     purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml
index f1fbd56..bd88ada 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml
@@ -1,5 +1,4 @@
 <data-labels>
-    <data-shared dataCategory="personal"
-    dataType="unrecognized"
+    <data-shared dataType="personal_data_type_unrecognized"
     purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml
index 5907462..742ed86 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml
@@ -1,31 +1,21 @@
 <data-labels>
-    <data-shared dataCategory="personal"
-        dataType="name"
-        ephemeral="true"
+    <data-shared dataType="personal_data_type_name"
         isSharingOptional="true"
         purposes="analytics|developer_communications" />
-    <data-shared dataCategory="personal"
-    dataType="email_address"
+    <data-shared dataType="personal_data_type_email_address"
     purposes="analytics" />
-    <data-shared dataCategory="personal"
-    dataType="physical_address"
+    <data-shared dataType="personal_data_type_physical_address"
     purposes="analytics" />
-    <data-shared dataCategory="personal"
-    dataType="phone_number"
+    <data-shared dataType="personal_data_type_phone_number"
     purposes="analytics" />
-    <data-shared dataCategory="personal"
-    dataType="race_ethnicity"
+    <data-shared dataType="personal_data_type_race_ethnicity"
     purposes="analytics" />
-    <data-shared dataCategory="personal"
-    dataType="political_or_religious_beliefs"
+    <data-shared dataType="personal_data_type_political_or_religious_beliefs"
     purposes="analytics" />
-    <data-shared dataCategory="personal"
-    dataType="sexual_orientation_or_gender_identity"
+    <data-shared dataType="personal_data_type_sexual_orientation_or_gender_identity"
     purposes="analytics" />
-    <data-shared dataCategory="personal"
-    dataType="personal_identifiers"
+    <data-shared dataType="personal_data_type_personal_identifiers"
     purposes="analytics" />
-    <data-shared dataCategory="personal"
-    dataType="other"
+    <data-shared dataType="personal_data_type_other"
     purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml
index 05fe159..d416063 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml
@@ -1,8 +1,6 @@
 <data-labels>
-    <data-shared dataCategory="photo_video"
-        dataType="photos"
+    <data-shared dataType="photo_video_data_type_photos"
         purposes="analytics" />
-    <data-shared dataCategory="photo_video"
-        dataType="videos"
+    <data-shared dataType="photo_video_data_type_videos"
         purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml
index a5de7be..3d932d6 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml
@@ -1,5 +1,4 @@
 <data-labels>
-    <data-shared dataCategory="search_and_browsing"
-        dataType="web_browsing_history"
+    <data-shared dataType="search_and_browsing_data_type_web_browsing_history"
         purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml
index f01e2df..704cb1c 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml
@@ -1,5 +1,4 @@
 <data-labels>
-    <data-shared dataCategory="storage"
-        dataType="files_docs"
+    <data-shared dataType="storage_data_type_files_docs"
         purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml
index f1fbd56..bd88ada 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml
@@ -1,5 +1,4 @@
 <data-labels>
-    <data-shared dataCategory="personal"
-    dataType="unrecognized"
+    <data-shared dataType="personal_data_type_unrecognized"
     purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml
index c5be684..a578d73 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml
@@ -1,5 +1,4 @@
 <data-labels>
-    <data-shared dataCategory="unrecognized"
-    dataType="email_address"
+    <data-shared dataType="unrecognized_data_type_email_address"
     purposes="analytics" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml
index 161057a..c0bd652 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml
@@ -1,11 +1,8 @@
 <data-labels>
-    <data-accessed dataCategory="location"
-        dataType="approx_location"
+    <data-accessed dataType="location_data_type_approx_location"
         purposes="app_functionality" />
-    <data-collected dataCategory="location"
-        dataType="precise_location"
+    <data-collected dataType="location_data_type_precise_location"
         purposes="app_functionality" />
-    <data-shared dataCategory="personal"
-        dataType="name"
+    <data-shared dataType="personal_data_type_name"
         purposes="app_functionality" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml
index bb45f42..d09fc3b 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml
@@ -1,7 +1,5 @@
 <data-labels>
-    <data-accessed dataCategory="location"
-        dataType="approx_location"
-        ephemeral="false"
+    <data-accessed dataType="location_data_type_approx_location"
         isSharingOptional="false"
         purposes="app_functionality" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml
index f927bba..6e7f812 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml
@@ -1,6 +1,4 @@
 <data-labels>
-    <data-accessed dataCategory="location"
-        dataType="approx_location"
-        ephemeral="false"
+    <data-accessed dataType="location_data_type_approx_location"
         purposes="app_functionality" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-ephemeral-collision.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-ephemeral-collision.xml
new file mode 100644
index 0000000..ee362fe
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-ephemeral-collision.xml
@@ -0,0 +1,14 @@
+<data-labels>
+    <data-collected dataType="photo_video_data_type_photos"
+        isCollectionOptional="true"
+        purposes="app_functionality" />
+    <data-collected-ephemeral dataType="location_data_type_approx_location"
+        isCollectionOptional="false"
+        purposes="app_functionality" />
+    <data-collected dataType="location_data_type_approx_location"
+        isCollectionOptional="true"
+        purposes="app_functionality" />
+    <data-collected-ephemeral dataType="contacts_data_type_contacts"
+        isCollectionOptional="true"
+        purposes="app_functionality" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-ephemeral.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-ephemeral.xml
new file mode 100644
index 0000000..79c9000
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-ephemeral.xml
@@ -0,0 +1,14 @@
+<data-labels>
+    <data-collected dataType="photo_video_data_type_photos"
+        isCollectionOptional="true"
+        purposes="app_functionality" />
+    <data-collected dataType="location_data_type_precise_location"
+        isCollectionOptional="true"
+        purposes="app_functionality" />
+    <data-collected-ephemeral dataType="location_data_type_approx_location"
+        isCollectionOptional="false"
+        purposes="app_functionality" />
+    <data-collected-ephemeral dataType="contacts_data_type_contacts"
+        isCollectionOptional="true"
+        purposes="app_functionality" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml
index ba11afb..801fada 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml
@@ -1,7 +1,5 @@
 <data-labels>
-    <data-collected dataCategory="location"
-        dataType="approx_location"
-        ephemeral="false"
+    <data-collected dataType="location_data_type_approx_location"
         isSharingOptional="false"
         purposes="app_functionality" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml
index 4b6d3977..1ada12d 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml
@@ -1,7 +1,5 @@
 <data-labels>
-    <data-collected dataCategory="location"
-        dataType="approx_location"
-        ephemeral="false"
+    <data-collected dataType="location_data_type_approx_location"
         isCollectionOptional="false"
         purposes="app_functionality" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml
index 7840b98..b327d88 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml
@@ -1,7 +1,5 @@
 <data-labels>
-    <data-shared dataCategory="location"
-        dataType="approx_location"
-        ephemeral="false"
+    <data-shared dataType="location_data_type_approx_location"
         isCollectionOptional="false"
         purposes="app_functionality" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml
index ccf77b0..34bd0de 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml
@@ -1,7 +1,5 @@
 <data-labels>
-    <data-shared dataCategory="location"
-        dataType="approx_location"
-        ephemeral="false"
+    <data-shared dataType="location_data_type_approx_location"
         isSharingOptional="false"
         purposes="app_functionality" />
 </data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml
index 14f9ef2..974ea69 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml
@@ -1,17 +1,17 @@
 <pbundle_as_map name="data_labels">
     <pbundle_as_map name="data_shared">
         <pbundle_as_map name="personal">
-    <pbundle_as_map name="name">
-        <int-array name="purposes" num="2">
-            <item value="2" />
-            <item value="3" />
-        </int-array>
-    </pbundle_as_map>
-    <pbundle_as_map name="email_address">
-        <int-array name="purposes" num="1">
-            <item value="2" />
-        </int-array>
-    </pbundle_as_map>
-</pbundle_as_map>
+            <pbundle_as_map name="name">
+                <int-array name="purposes" num="2">
+                    <item value="2" />
+                    <item value="3" />
+                </int-array>
+            </pbundle_as_map>
+            <pbundle_as_map name="email_address">
+                <int-array name="purposes" num="1">
+                    <item value="2" />
+                </int-array>
+            </pbundle_as_map>
+        </pbundle_as_map>
     </pbundle_as_map>
 </pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml
index 1c87de9..62c26ab 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml
@@ -7,7 +7,6 @@
             <item value="3" />
         </int-array>
         <boolean name="is_sharing_optional" value="true" />
-        <boolean name="ephemeral" value="true" />
     </pbundle_as_map>
     <pbundle_as_map name="email_address">
         <int-array name="purposes" num="1">
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml
index ddefc18..df000aa 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml
@@ -5,7 +5,6 @@
                 <int-array name="purposes" num="1">
                     <item value="1"/>
                 </int-array>
-                <boolean name="ephemeral" value="false"/>
             </pbundle_as_map>
         </pbundle_as_map>
     </pbundle_as_map>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-ephemeral.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-ephemeral.xml
new file mode 100644
index 0000000..c671c4b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-ephemeral.xml
@@ -0,0 +1,38 @@
+<pbundle_as_map name="data_labels">
+    <pbundle_as_map name="data_collected">
+        <pbundle_as_map name="photo_video">
+            <pbundle_as_map name="photos">
+                <int-array name="purposes" num="1">
+                    <item value="1"/>
+                </int-array>
+                <boolean name="is_collection_optional" value="true"/>
+                <boolean name="ephemeral" value="false"/>
+            </pbundle_as_map>
+        </pbundle_as_map>
+        <pbundle_as_map name="location">
+            <pbundle_as_map name="precise_location">
+                <int-array name="purposes" num="1">
+                    <item value="1"/>
+                </int-array>
+                <boolean name="is_collection_optional" value="true"/>
+                <boolean name="ephemeral" value="false"/>
+            </pbundle_as_map>
+            <pbundle_as_map name="approx_location">
+                <int-array name="purposes" num="1">
+                    <item value="1"/>
+                </int-array>
+                <boolean name="is_collection_optional" value="false"/>
+                <boolean name="ephemeral" value="true"/>
+            </pbundle_as_map>
+        </pbundle_as_map>
+        <pbundle_as_map name="contacts">
+            <pbundle_as_map name="contacts">
+                <int-array name="purposes" num="1">
+                    <item value="1"/>
+                </int-array>
+                <boolean name="is_collection_optional" value="true"/>
+                <boolean name="ephemeral" value="true"/>
+            </pbundle_as_map>
+        </pbundle_as_map>
+    </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml
index 3864f98..0edd8fa26 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml
@@ -6,7 +6,6 @@
                     <item value="1"/>
                 </int-array>
                 <boolean name="is_sharing_optional" value="false"/>
-                <boolean name="ephemeral" value="false"/>
             </pbundle_as_map>
         </pbundle_as_map>
     </pbundle_as_map>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml
index 8997f4f..84456da 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml
@@ -1,9 +1,7 @@
 <safety-labels version="12345">
     <data-labels>
-        <data-shared dataCategory="location"
-            dataType="approx_location"
+        <data-shared dataType="location_data_type_approx_location"
             isSharingOptional="false"
-            ephemeral="false"
             purposes="app_functionality" />
     </data-labels>
 </safety-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml
index a966fda..fa2a3f8 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml
@@ -8,7 +8,6 @@
                         <item value="1"/>
                     </int-array>
                     <boolean name="is_sharing_optional" value="false"/>
-                    <boolean name="ephemeral" value="false"/>
                 </pbundle_as_map>
             </pbundle_as_map>
         </pbundle_as_map>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-url.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-bool.xml
similarity index 100%
rename from tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-url.xml
rename to tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-bool.xml
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml
index 6fe86c3..f01d7d2 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml
@@ -1 +1 @@
-<system-app-safety-label url="www.example.com"></system-app-safety-label>
\ No newline at end of file
+<system-app-safety-label declaration="true"></system-app-safety-label>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/missing-url.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/missing-bool.xml
similarity index 100%
rename from tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/missing-url.xml
rename to tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/missing-bool.xml
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml
index f96535b..fad631b 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml
@@ -1,3 +1,3 @@
 <pbundle_as_map name="system_app_safety_label">
-    <string name="url" value="www.example.com"/>
+    <boolean name="declaration" value="true"/>
 </pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
index 8f854ad..41b32b5 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
@@ -1,15 +1,13 @@
 <app-metadata-bundles version="123">
     <safety-labels version="12345">
         <data-labels>
-            <data-shared dataCategory="location"
-                dataType="approx_location"
+            <data-shared
+                dataType="location_data_type_approx_location"
                 isSharingOptional="false"
-                ephemeral="false"
                 purposes="app_functionality" />
-            <data-shared dataCategory="location"
-                dataType="precise_location"
+            <data-shared
+                dataType="location_data_type_precise_location"
                 isSharingOptional="true"
-                ephemeral="true"
                 purposes="app_functionality|analytics" />
         </data-labels>
         <security-labels
@@ -19,7 +17,7 @@
         <third-party-verification url="www.example.com">
         </third-party-verification>
     </safety-labels>
-    <system-app-safety-label url="www.example.com">
+    <system-app-safety-label declaration="true">
     </system-app-safety-label>
     <transparency-info>
         <developer-info
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
index 8f1dc64..c11ac43 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
@@ -10,7 +10,6 @@
                             <item value="1"/>
                         </int-array>
                         <boolean name="is_sharing_optional" value="false"/>
-                        <boolean name="ephemeral" value="false"/>
                     </pbundle_as_map>
                     <pbundle_as_map name="precise_location">
                         <int-array name="purposes" num="2">
@@ -18,7 +17,6 @@
                             <item value="2"/>
                         </int-array>
                         <boolean name="is_sharing_optional" value="true"/>
-                        <boolean name="ephemeral" value="true"/>
                     </pbundle_as_map>
                 </pbundle_as_map>
             </pbundle_as_map>
@@ -32,7 +30,7 @@
         </pbundle_as_map>
     </pbundle_as_map>
     <pbundle_as_map name="system_app_safety_label">
-        <string name="url" value="www.example.com"/>
+        <boolean name="declaration" value="true"/>
     </pbundle_as_map>
     <pbundle_as_map name="transparency_info">
         <pbundle_as_map name="developer_info">