Merge "Remove visitPersonUris flag" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index e551789..fda66fa 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -21016,6 +21016,7 @@
     method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
     method public android.view.View onCreateInputView();
     method protected void onCurrentInputMethodSubtypeChanged(android.view.inputmethod.InputMethodSubtype);
+    method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public void onCustomImeSwitcherButtonRequestedVisible(boolean);
     method public void onDisplayCompletions(android.view.inputmethod.CompletionInfo[]);
     method public boolean onEvaluateFullscreenMode();
     method @CallSuper public boolean onEvaluateInputViewShown();
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index bcb5b36..d5e696d 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -7,4 +7,12 @@
   namespace: "supervision"
   description: "Flag to enable the SupervisionService"
   bug: "340351729"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "supervision_api_on_wear"
+  is_exported: true
+  namespace: "supervision"
+  description: "Flag to enable the SupervisionService on Wear devices"
+  bug: "373358935"
+}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 34c3f57..e9e8578 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -38,6 +38,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AttributeSet;
+import android.util.EmptyArray;
 import android.util.Pair;
 import android.util.Slog;
 
@@ -565,10 +566,7 @@
                                     usesSdkLibrariesVersionsMajor, usesSdkLibVersionMajor,
                                     /*allowDuplicates=*/ true);
 
-                            // We allow ":" delimiters in the SHA declaration as this is the format
-                            // emitted by the certtool making it easy for developers to copy/paste.
-                            // TODO(372862145): Add test for this replacement
-                            usesSdkCertDigest = usesSdkCertDigest.replace(":", "").toLowerCase();
+                            usesSdkCertDigest = normalizeCertDigest(usesSdkCertDigest);
 
                             if ("".equals(usesSdkCertDigest)) {
                                 // Test-only uses-sdk-library empty certificate digest override.
@@ -618,18 +616,23 @@
                                     usesStaticLibrariesVersions, usesStaticLibVersion,
                                     /*allowDuplicates=*/ true);
 
-                            // We allow ":" delimiters in the SHA declaration as this is the format
-                            // emitted by the certtool making it easy for developers to copy/paste.
-                            // TODO(372862145): Add test for this replacement
-                            usesStaticLibCertDigest =
-                                    usesStaticLibCertDigest.replace(":", "").toLowerCase();
+                            usesStaticLibCertDigest = normalizeCertDigest(usesStaticLibCertDigest);
 
-                            // TODO(372862145): Add support for multiple signer for app targeting
-                            //  O-MR1
+                            ParseResult<String[]> certResult =
+                                    parseAdditionalCertificates(input, parser);
+                            if (certResult.isError()) {
+                                return input.error(certResult);
+                            }
+                            String[] additionalCertSha256Digests = certResult.getResult();
+                            String[] certSha256Digests =
+                                    new String[additionalCertSha256Digests.length + 1];
+                            certSha256Digests[0] = usesStaticLibCertDigest;
+                            System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
+                                    1, additionalCertSha256Digests.length);
+
                             usesStaticLibrariesCertDigests = ArrayUtils.appendElement(
                                     String[].class, usesStaticLibrariesCertDigests,
-                                    new String[]{usesStaticLibCertDigest},
-                                    /*allowDuplicates=*/ true);
+                                    certSha256Digests, /*allowDuplicates=*/ true);
                             break;
                         case TAG_SDK_LIBRARY:
                             isSdkLibrary = true;
@@ -809,6 +812,43 @@
                         declaredLibraries));
     }
 
+    private static ParseResult<String[]> parseAdditionalCertificates(ParseInput input,
+            XmlResourceParser parser) throws XmlPullParserException, IOException {
+        String[] certSha256Digests = EmptyArray.STRING;
+        final int depth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG
+                || parser.getDepth() > depth)) {
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+            final String nodeName = parser.getName();
+            if (nodeName.equals("additional-certificate")) {
+                String certSha256Digest = parser.getAttributeValue(
+                        ANDROID_RES_NAMESPACE, "certDigest");
+                if (TextUtils.isEmpty(certSha256Digest)) {
+                    return input.error("Bad additional-certificate declaration with empty"
+                            + " certDigest:" + certSha256Digest);
+                }
+
+                certSha256Digest = normalizeCertDigest(certSha256Digest);
+                certSha256Digests = ArrayUtils.appendElement(String.class,
+                        certSha256Digests, certSha256Digest);
+            }
+        }
+
+        return input.success(certSha256Digests);
+    }
+
+    /**
+     * We allow ":" delimiters in the SHA declaration as this is the format emitted by the
+     * certtool making it easy for developers to copy/paste.
+     */
+    private static String normalizeCertDigest(String certDigest) {
+        return certDigest.replace(":", "").toLowerCase();
+    }
+
     private static boolean isDeviceAdminReceiver(
             XmlResourceParser parser, boolean applicationHasBindDeviceAdminPermission)
             throws XmlPullParserException, IOException {
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8c3f0ef..ae83668 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -55,6 +55,7 @@
 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER;
 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
 import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING;
+import static android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API;
 import static android.view.inputmethod.Flags.ctrlShiftShortcut;
 import static android.view.inputmethod.Flags.predictiveBackIme;
 
@@ -4392,6 +4393,39 @@
     }
 
     /**
+     * Called when the requested visibility of a custom IME Switcher button changes.
+     *
+     * <p>When the system provides an IME navigation bar, it may decide to show an IME Switcher
+     * button inside this bar. However, the IME can request hiding the bar provided by the system
+     * with {@code getWindowInsetsController().hide(captionBar())} (the IME navigation bar provides
+     * {@link Type#captionBar() captionBar} insets to the IME window). If the request is successful,
+     * then it becomes the IME's responsibility to provide a custom IME Switcher button in its
+     * input view, with equivalent functionality.</p>
+     *
+     * <p>This custom button is only requested to be visible when the system provides the IME
+     * navigation bar, both the bar and the IME Switcher button inside it should be visible,
+     * but the IME successfully requested to hide the bar. This does not depend on the current
+     * visibility of the IME. It could be called with {@code true} while the IME is hidden, in
+     * which case the IME should prepare to show the button as soon as the IME itself is shown.</p>
+     *
+     * <p>This is only called when the requested visibility changes. The default value is
+     * {@code false} and as such, this will not be called initially if the resulting value is
+     * {@code false}.</p>
+     *
+     * <p>This can be called at any time after {@link #onCreate}, even if the IME is not currently
+     * visible. However, this is not guaranteed to be called before the IME is shown, as it depends
+     * on when the IME requested hiding the IME navigation bar. If the request is sent during
+     * the showing flow (e.g. during {@link #onStartInputView}), this will be called shortly after
+     * {@link #onWindowShown}, but before the first IME frame is drawn.</p>
+     *
+     * @param visible whether the button is requested visible or not.
+     */
+    @FlaggedApi(FLAG_IME_SWITCHER_REVAMP_API)
+    public void onCustomImeSwitcherButtonRequestedVisible(boolean visible) {
+        // Intentionally empty
+    }
+
+    /**
      * Called when the IME switch button was clicked from the client. Depending on the number of
      * enabled IME subtypes, this will either switch to the next IME/subtype, or show the input
      * method picker dialog.
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index b08454d..38be8d9 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -41,6 +41,7 @@
 import android.view.WindowInsetsController.Appearance;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
 
@@ -178,6 +179,9 @@
 
         private boolean mDrawLegacyNavigationBarBackground;
 
+        /** Whether a custom IME Switcher button should be visible. */
+        private boolean mCustomImeSwitcherVisible;
+
         private final Rect mTempRect = new Rect();
         private final int[] mTempPos = new int[2];
 
@@ -265,6 +269,7 @@
                     // IME navigation bar.
                     boolean visible = insets.isVisible(captionBar());
                     mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE);
+                    checkCustomImeSwitcherVisibility();
                 }
                 return view.onApplyWindowInsets(insets);
             });
@@ -491,6 +496,8 @@
                     mShouldShowImeSwitcherWhenImeIsShown;
             mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
 
+            checkCustomImeSwitcherVisibility();
+
             mService.mWindow.getWindow().getDecorView().getWindowInsetsController()
                     .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight(imeDrawsImeNavBar));
 
@@ -616,12 +623,33 @@
                     && mNavigationBarFrame.getVisibility() == View.VISIBLE;
         }
 
+        /**
+         * Checks if a custom IME Switcher button should be visible, and notifies the IME when this
+         * state changes. This can only be {@code true} if three conditions are met:
+         *
+         * <li>The IME should draw the IME navigation bar.</li>
+         * <li>The IME Switcher button should be visible when the IME is visible.</li>
+         * <li>The IME navigation bar should be visible, but was requested hidden by the IME.</li>
+         */
+        private void checkCustomImeSwitcherVisibility() {
+            if (!Flags.imeSwitcherRevampApi()) {
+                return;
+            }
+            final boolean visible = mImeDrawsImeNavBar && mShouldShowImeSwitcherWhenImeIsShown
+                    && mNavigationBarFrame != null && !isShown();
+            if (visible != mCustomImeSwitcherVisible) {
+                mCustomImeSwitcherVisible = visible;
+                mService.onCustomImeSwitcherButtonRequestedVisible(mCustomImeSwitcherVisible);
+            }
+        }
+
         @Override
         public String toDebugString() {
             return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar
                     + " mNavigationBarFrame=" + mNavigationBarFrame
                     + " mShouldShowImeSwitcherWhenImeIsShown="
                     + mShouldShowImeSwitcherWhenImeIsShown
+                    + " mCustomImeSwitcherVisible="  + mCustomImeSwitcherVisible
                     + " mAppearance=0x" + Integer.toHexString(mAppearance)
                     + " mDarkIntensity=" + mDarkIntensity
                     + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index a382d79..f39508d 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -151,6 +151,7 @@
         ":HelloWorldUsingSdk1And2",
         ":HelloWorldUsingSdkMalformedNegativeVersion",
         ":CtsStaticSharedLibConsumerApp1",
+        ":CtsStaticSharedLibConsumerApp3",
     ],
 }
 
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 3f7c83a..5d8ff87 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -41,6 +41,8 @@
             value="/data/local/tmp/tests/coretests/pm/HelloWorldSdk1.apk"/>
         <option name="push-file" key="CtsStaticSharedLibConsumerApp1.apk"
             value="/data/local/tmp/tests/coretests/pm/CtsStaticSharedLibConsumerApp1.apk"/>
+        <option name="push-file" key="CtsStaticSharedLibConsumerApp3.apk"
+            value="/data/local/tmp/tests/coretests/pm/CtsStaticSharedLibConsumerApp3.apk"/>
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
index d4618d7..0db49a7 100644
--- a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
+++ b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
@@ -72,6 +72,12 @@
     private static final String TEST_APP_USING_SDK_MALFORMED_VERSION =
             "HelloWorldUsingSdkMalformedNegativeVersion.apk";
     private static final String TEST_APP_USING_STATIC_LIB = "CtsStaticSharedLibConsumerApp1.apk";
+    private static final String TEST_APP_USING_STATIC_LIB_TWO_CERTS =
+            "CtsStaticSharedLibConsumerApp3.apk";
+    private static final String STATIC_LIB_CERT_1 =
+            "70fbd440503ec0bf41f3f21fcc83ffd39880133c27deb0945ed677c6f31d72fb";
+    private static final String STATIC_LIB_CERT_2 =
+            "e49582ff3a0aa4c5589fc5feaac6b7d6e757199dd0c6742df7bf37c2ffef95f5";
     private static final String TEST_SDK1 = "HelloWorldSdk1.apk";
     private static final String TEST_SDK1_PACKAGE = "com.test.sdk1_1";
     private static final String TEST_SDK1_NAME = "com.test.sdk1";
@@ -86,7 +92,7 @@
 
     @Before
     public void setUp() throws IOException {
-        mTmpDir = mTemporaryFolder.newFolder("DexMetadataHelperTest");
+        mTmpDir = mTemporaryFolder.newFolder("ApkLiteParseUtilsTest");
     }
 
     @After
@@ -108,9 +114,8 @@
         assertThat(baseApk.getUsesSdkLibrariesVersionsMajor()).asList().containsExactly(
                 TEST_SDK1_VERSION, TEST_SDK2_VERSION
         );
-        for (String[] certDigests: baseApk.getUsesSdkLibrariesCertDigests()) {
-            assertThat(certDigests).asList().containsExactly("");
-        }
+        String[][] expectedCerts = {{""}, {""}};
+        assertThat(baseApk.getUsesSdkLibrariesCertDigests()).isEqualTo(expectedCerts);
     }
 
     @SuppressLint("CheckResult")
@@ -126,18 +131,13 @@
         ApkLite baseApk = result.getResult();
 
         String[][] liteCerts = baseApk.getUsesSdkLibrariesCertDigests();
-        assertThat(liteCerts).isNotNull();
-        for (String[] certDigests: liteCerts) {
-            assertThat(certDigests).asList().containsExactly(certDigest);
-        }
+        String[][] expectedCerts = {{certDigest}, {certDigest}};
+        assertThat(liteCerts).isEqualTo(expectedCerts);
 
         // Same for package parser
         AndroidPackage pkg = mPackageParser2.parsePackage(apkFile, 0, true).hideAsFinal();
         String[][] pkgCerts = pkg.getUsesSdkLibrariesCertDigests();
-        assertThat(pkgCerts).isNotNull();
-        for (int i = 0; i < liteCerts.length; i++) {
-            assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
-        }
+        assertThat(liteCerts).isEqualTo(pkgCerts);
     }
 
 
@@ -160,9 +160,7 @@
 
         String[][] liteCerts = baseApk.getUsesSdkLibrariesCertDigests();
         String[][] pkgCerts = pkg.getUsesSdkLibrariesCertDigests();
-        for (int i = 0; i < liteCerts.length; i++) {
-            assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
-        }
+        assertThat(liteCerts).isEqualTo(pkgCerts);
     }
 
     @SuppressLint("CheckResult")
@@ -184,9 +182,27 @@
 
         String[][] liteCerts = baseApk.getUsesStaticLibrariesCertDigests();
         String[][] pkgCerts = pkg.getUsesStaticLibrariesCertDigests();
-        for (int i = 0; i < liteCerts.length; i++) {
-            assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
-        }
+        assertThat(liteCerts).isEqualTo(pkgCerts);
+    }
+
+    @Test
+    public void testParseApkLite_getUsesStaticLibrary_twoCerts()
+            throws Exception {
+        File apkFile = copyApkToTmpDir(TEST_APP_USING_STATIC_LIB_TWO_CERTS);
+        ParseResult<ApkLite> result = ApkLiteParseUtils
+                .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+        assertThat(result.isError()).isFalse();
+        ApkLite baseApk = result.getResult();
+
+        // There are two certs.
+        String[][] expectedCerts = {{STATIC_LIB_CERT_1, STATIC_LIB_CERT_2}};
+        String[][] liteCerts = baseApk.getUsesStaticLibrariesCertDigests();
+        assertThat(liteCerts).isEqualTo(expectedCerts);
+
+        // And they are same as package parser.
+        AndroidPackage pkg = mPackageParser2.parsePackage(apkFile, 0, true).hideAsFinal();
+        String[][] pkgCerts = pkg.getUsesStaticLibrariesCertDigests();
+        assertThat(liteCerts).isEqualTo(pkgCerts);
     }
 
     @SuppressLint("CheckResult")
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 0b515f5..5f42bb1 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -475,6 +475,6 @@
 
         override fun hideCurrentInputMethod() {}
 
-        override fun updateBubbleBarLocation(location: BubbleBarLocation) {}
+        override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {}
     }
 }
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index 0d742cc..6ac36a3 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -375,7 +375,7 @@
             override fun hideCurrentInputMethod() {
             }
 
-            override fun updateBubbleBarLocation(location: BubbleBarLocation) {
+            override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {
             }
         }
     }
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 00d9a93..0044593 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -351,7 +351,7 @@
 
             override fun hideCurrentInputMethod() {}
 
-            override fun updateBubbleBarLocation(location: BubbleBarLocation) {}
+            override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {}
         }
     }
 
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
index 191875d..84a22b8 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
@@ -15,6 +15,7 @@
  */
 package com.android.wm.shell.shared.bubbles
 
+import android.annotation.IntDef
 import android.os.Parcel
 import android.os.Parcelable
 
@@ -60,4 +61,36 @@
             override fun newArray(size: Int) = arrayOfNulls<BubbleBarLocation>(size)
         }
     }
+
+    /** Define set of constants that allow to determine why location changed. */
+    @IntDef(
+        UpdateSource.DRAG_BAR,
+        UpdateSource.DRAG_BUBBLE,
+        UpdateSource.DRAG_EXP_VIEW,
+        UpdateSource.A11Y_ACTION_BAR,
+        UpdateSource.A11Y_ACTION_BUBBLE,
+        UpdateSource.A11Y_ACTION_EXP_VIEW,
+    )
+    @Retention(AnnotationRetention.SOURCE)
+    annotation class UpdateSource {
+        companion object {
+            /** Location changed from dragging the bar */
+            const val DRAG_BAR = 1
+
+            /** Location changed from dragging the bubble */
+            const val DRAG_BUBBLE = 2
+
+            /** Location changed from dragging the expanded view */
+            const val DRAG_EXP_VIEW = 3
+
+            /** Location changed via a11y action on the bar */
+            const val A11Y_ACTION_BAR = 4
+
+            /** Location changed via a11y action on the bubble */
+            const val A11Y_ACTION_BUBBLE = 5
+
+            /** Location changed via a11y action on the expanded view */
+            const val A11Y_ACTION_EXP_VIEW = 6
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 14f8cc7..0fd98ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -740,8 +740,10 @@
     /**
      * Update bubble bar location and trigger and update to listeners
      */
-    public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+    public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation,
+            @BubbleBarLocation.UpdateSource int source) {
         if (canShowAsBubbleBar()) {
+            BubbleBarLocation previousLocation = mBubblePositioner.getBubbleBarLocation();
             mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
             if (mLayerView != null && !mLayerView.isExpandedViewDragged()) {
                 mLayerView.updateExpandedView();
@@ -749,13 +751,47 @@
             BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
             bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation;
             mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
+
+            logBubbleBarLocationIfChanged(bubbleBarLocation, previousLocation, source);
+        }
+    }
+
+    private void logBubbleBarLocationIfChanged(BubbleBarLocation location,
+            BubbleBarLocation previous,
+            @BubbleBarLocation.UpdateSource int source) {
+        if (mLayerView == null) {
+            return;
+        }
+        boolean isRtl = mLayerView.isLayoutRtl();
+        boolean wasLeft = previous.isOnLeft(isRtl);
+        boolean onLeft = location.isOnLeft(isRtl);
+        if (wasLeft == onLeft) {
+            // No changes, skip logging
+            return;
+        }
+        switch (source) {
+            case BubbleBarLocation.UpdateSource.DRAG_BAR:
+            case BubbleBarLocation.UpdateSource.A11Y_ACTION_BAR:
+                mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR
+                        : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR);
+                break;
+            case BubbleBarLocation.UpdateSource.DRAG_BUBBLE:
+            case BubbleBarLocation.UpdateSource.A11Y_ACTION_BUBBLE:
+                mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE
+                        : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE);
+                break;
+            case BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW:
+            case BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW:
+                // TODO(b/349845968): move logging from BubbleBarLayerView to here
+                break;
         }
     }
 
     /**
      * Animate bubble bar to the given location. The location change is transient. It does not
      * update the state of the bubble bar.
-     * To update bubble bar pinned location, use {@link #setBubbleBarLocation(BubbleBarLocation)}.
+     * To update bubble bar pinned location, use
+     * {@link #setBubbleBarLocation(BubbleBarLocation, int)}.
      */
     public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
         if (canShowAsBubbleBar()) {
@@ -2568,9 +2604,10 @@
         }
 
         @Override
-        public void setBubbleBarLocation(BubbleBarLocation location) {
+        public void setBubbleBarLocation(BubbleBarLocation location,
+                @BubbleBarLocation.UpdateSource int source) {
             mMainExecutor.execute(() ->
-                    mController.setBubbleBarLocation(location));
+                    mController.setBubbleBarLocation(location, source));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
index ec4854b..6423eed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
@@ -32,7 +32,10 @@
     fun isStackExpanded(): Boolean
     fun isShowingAsBubbleBar(): Boolean
     fun hideCurrentInputMethod()
-    fun updateBubbleBarLocation(location: BubbleBarLocation)
+    fun updateBubbleBarLocation(
+        location: BubbleBarLocation,
+        @BubbleBarLocation.UpdateSource source: Int,
+    )
 
     companion object {
         /**
@@ -82,8 +85,11 @@
                     controller.hideCurrentInputMethod()
                 }
 
-                override fun updateBubbleBarLocation(location: BubbleBarLocation) {
-                    controller.bubbleBarLocation = location
+                override fun updateBubbleBarLocation(
+                    location: BubbleBarLocation,
+                    @BubbleBarLocation.UpdateSource source: Int,
+                ) {
+                    controller.setBubbleBarLocation(location, source)
                 }
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 1855b93..9c2d3543 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -44,7 +44,7 @@
 
     oneway void showUserEducation(in int positionX, in int positionY) = 8;
 
-    oneway void setBubbleBarLocation(in BubbleBarLocation location) = 9;
+    oneway void setBubbleBarLocation(in BubbleBarLocation location, in int source) = 9;
 
     oneway void updateBubbleBarTopOnScreen(in int topOnScreen) = 10;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 272dfec..3764bcd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -637,11 +637,13 @@
                 return true;
             }
             if (action == R.id.action_move_bubble_bar_left) {
-                mManager.updateBubbleBarLocation(BubbleBarLocation.LEFT);
+                mManager.updateBubbleBarLocation(BubbleBarLocation.LEFT,
+                        BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW);
                 return true;
             }
             if (action == R.id.action_move_bubble_bar_right) {
-                mManager.updateBubbleBarLocation(BubbleBarLocation.RIGHT);
+                mManager.updateBubbleBarLocation(BubbleBarLocation.RIGHT,
+                        BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW);
                 return true;
             }
             return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 1f77abe..0c05e3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -441,7 +441,8 @@
 
         @Override
         public void onRelease(@NonNull BubbleBarLocation location) {
-            mBubbleController.setBubbleBarLocation(location);
+            mBubbleController.setBubbleBarLocation(location,
+                    BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW);
             if (location != mInitialLocation) {
                 BubbleLogger.Event event = location.isOnLeft(isLayoutRtl())
                         ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 19a73f3..cc0e1df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -55,6 +55,7 @@
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
@@ -1098,11 +1099,16 @@
 
     void setSideStagePosition(@SplitPosition int sideStagePosition,
             @Nullable WindowContainerTransaction wct) {
+        setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
+    }
+
+    private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
+            @Nullable WindowContainerTransaction wct) {
         if (mSideStagePosition == sideStagePosition) return;
         mSideStagePosition = sideStagePosition;
         sendOnStagePositionChanged();
 
-        if (mSideStage.mVisible) {
+        if (mSideStage.mVisible && updateBounds) {
             if (wct == null) {
                 // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
                 onLayoutSizeChanged(mSplitLayout);
@@ -1193,7 +1199,6 @@
         if (!isSplitActive()) return;
 
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
         applyExitSplitScreen(childrenToTop, wct, exitReason);
     }
 
@@ -1593,13 +1598,6 @@
         }
         if (present) {
             updateRecentTasksSplitPair();
-        } else if (mMainStage.getChildCount() == 0 && mSideStage.getChildCount() == 0) {
-            mRecentTasks.ifPresent(recentTasks -> {
-                // remove the split pair mapping from recentTasks, and disable further updates
-                // to splits in the recents until we enter split again.
-                recentTasks.removeSplitPair(taskId);
-            });
-            exitSplitScreen(mMainStage, EXIT_REASON_ROOT_TASK_VANISHED);
         }
 
         for (int i = mListeners.size() - 1; i >= 0; --i) {
diff --git a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
new file mode 100644
index 0000000..651e401
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+
+<resources>
+    <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw600dp/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..10e630d
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<resources>
+    <!-- For portrait direction in unfold foldable device, we don't need keyguard_smartspace_top_offset-->
+    <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
index c574d1f..7feea6e 100644
--- a/packages/SystemUI/customization/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -33,4 +33,10 @@
     <dimen name="small_clock_height">114dp</dimen>
     <dimen name="small_clock_padding_top">28dp</dimen>
     <dimen name="clock_padding_start">28dp</dimen>
+
+    <!-- When large clock is showing, offset the smartspace by this amount -->
+    <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
+    <!--Dimens used in both lockscreen  preview and smartspace -->
+    <dimen name="date_weather_view_height">24dp</dimen>
+    <dimen name="enhanced_smartspace_height">104dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
index e142169..58fe2c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
@@ -36,6 +36,7 @@
 
 private const val USER_ID = 22
 private const val OWNER_ID = 10
+private const val PASSWORD_ID = 30
 private const val OPERATION_ID = 100L
 private const val MAX_ATTEMPTS = 5
 
@@ -247,7 +248,11 @@
 private fun pinRequest(credentialOwner: Int = USER_ID): BiometricPromptRequest.Credential.Pin =
     BiometricPromptRequest.Credential.Pin(
         promptInfo(),
-        BiometricUserInfo(userId = USER_ID, deviceCredentialOwnerId = credentialOwner),
+        BiometricUserInfo(
+            userId = USER_ID,
+            deviceCredentialOwnerId = credentialOwner,
+            userIdForPasswordEntry = PASSWORD_ID,
+        ),
         BiometricOperationInfo(OPERATION_ID),
     )
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index ecc62e9..87ab3c8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -69,7 +69,9 @@
         get() =
             kosmos.fakeSystemBarUtilsProxy.getStatusBarHeight() +
                 context.resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) +
-                context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+                context.resources.getDimensionPixelSize(
+                    customR.dimen.keyguard_smartspace_top_offset
+                )
 
     private val LARGE_CLOCK_TOP
         get() =
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 7d55169..89da465 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -13,11 +13,20 @@
  */
 package com.android.systemui.plugins.clocks
 
+import android.content.Context
 import android.graphics.Rect
 import android.graphics.drawable.Drawable
+import android.util.DisplayMetrics
 import android.view.View
 import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
 import com.android.internal.annotations.Keep
+import com.android.internal.policy.SystemBarUtils
 import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.plugins.Plugin
 import com.android.systemui.plugins.annotations.GeneratedImport
@@ -149,7 +158,7 @@
 
     @ProtectedReturn("return constraints;")
     /** Custom constraints to apply to preview ConstraintLayout. */
-    fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet
+    fun applyPreviewConstraints(context: Context, constraints: ConstraintSet): ConstraintSet
 
     fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel)
 }
@@ -169,13 +178,84 @@
         return constraints
     }
 
-    override fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet {
-        return constraints
+    override fun applyPreviewConstraints(
+        context: Context,
+        constraints: ConstraintSet,
+    ): ConstraintSet {
+        return applyDefaultPreviewConstraints(context, constraints)
     }
 
     override fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) {
         // Default clock doesn't need detailed control of view
     }
+
+    companion object {
+        fun applyDefaultPreviewConstraints(
+            context: Context,
+            constraints: ConstraintSet,
+        ): ConstraintSet {
+            constraints.apply {
+                val lockscreenClockViewLargeId = getId(context, "lockscreen_clock_view_large")
+                constrainWidth(lockscreenClockViewLargeId, WRAP_CONTENT)
+                constrainHeight(lockscreenClockViewLargeId, WRAP_CONTENT)
+                constrainMaxHeight(lockscreenClockViewLargeId, 0)
+
+                val largeClockTopMargin =
+                    SystemBarUtils.getStatusBarHeight(context) +
+                        getDimen(context, "small_clock_padding_top") +
+                        getDimen(context, "keyguard_smartspace_top_offset") +
+                        getDimen(context, "date_weather_view_height") +
+                        getDimen(context, "enhanced_smartspace_height")
+                connect(lockscreenClockViewLargeId, TOP, PARENT_ID, TOP, largeClockTopMargin)
+                connect(lockscreenClockViewLargeId, START, PARENT_ID, START)
+                connect(lockscreenClockViewLargeId, END, PARENT_ID, END)
+
+                // In preview, we'll show UDFPS icon for UDFPS devices
+                // and nothing for non-UDFPS devices,
+                // and we're not planning to add this vide in clockHostView
+                // so we only need position of device entry icon to constrain clock
+                // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
+                val bottomPaddingPx = getDimen(context, "lock_icon_margin_bottom")
+                val defaultDensity =
+                    DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+                        DisplayMetrics.DENSITY_DEFAULT.toFloat()
+                val lockIconRadiusPx = (defaultDensity * 36).toInt()
+                val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
+
+                connect(lockscreenClockViewLargeId, BOTTOM, PARENT_ID, BOTTOM, clockBottomMargin)
+                val smallClockViewId = getId(context, "lockscreen_clock_view")
+                constrainWidth(smallClockViewId, WRAP_CONTENT)
+                constrainHeight(smallClockViewId, getDimen(context, "small_clock_height"))
+                connect(
+                    smallClockViewId,
+                    START,
+                    PARENT_ID,
+                    START,
+                    getDimen(context, "clock_padding_start") +
+                        getDimen(context, "status_view_margin_horizontal"),
+                )
+                val smallClockTopMargin =
+                    getDimen(context, "keyguard_clock_top_margin") +
+                        SystemBarUtils.getStatusBarHeight(context)
+                connect(smallClockViewId, TOP, PARENT_ID, TOP, smallClockTopMargin)
+            }
+            return constraints
+        }
+
+        fun getId(context: Context, name: String): Int {
+            val packageName = context.packageName
+            val res = context.packageManager.getResourcesForApplication(packageName)
+            val id = res.getIdentifier(name, "id", packageName)
+            return id
+        }
+
+        fun getDimen(context: Context, name: String): Int {
+            val packageName = context.packageName
+            val res = context.packageManager.getResourcesForApplication(packageName)
+            val id = res.getIdentifier(name, "dimen", packageName)
+            return if (id == 0) 0 else res.getDimensionPixelSize(id)
+        }
+    }
 }
 
 /** Events that should call when various rendering parameters change */
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 2a27b47..4a53df9 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -24,7 +24,6 @@
     <!-- margin from keyguard status bar to clock. For split shade it should be
          keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp -->
     <dimen name="keyguard_clock_top_margin">8dp</dimen>
-    <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
 
     <!-- QS-->
     <dimen name="qs_panel_padding_top">16dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 393631e..26f32ef 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -126,6 +126,4 @@
     <dimen name="controls_content_padding">24dp</dimen>
     <dimen name="control_list_vertical_spacing">8dp</dimen>
     <dimen name="control_list_horizontal_spacing">16dp</dimen>
-    <!-- For portrait direction in unfold foldable device, we don't need keyguard_smartspace_top_offset-->
-    <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7fa2879..67eb5b0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -815,8 +815,7 @@
     <dimen name="keyguard_clock_top_margin">18dp</dimen>
     <!-- The amount to shift the clocks during a small/large transition -->
     <dimen name="keyguard_clock_switch_y_shift">14dp</dimen>
-    <!-- When large clock is showing, offset the smartspace by this amount -->
-    <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
+
     <!-- The amount to translate lockscreen elements on the GONE->AOD transition -->
     <dimen name="keyguard_enter_from_top_translation_y">-100dp</dimen>
     <!-- The amount to translate lockscreen elements on the GONE->AOD transition, on device fold -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 11dde6a..71d4e9a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -147,7 +147,7 @@
         mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
                 R.dimen.keyguard_clock_switch_y_shift);
         mSmartspaceTopOffset = (int) (mContext.getResources().getDimensionPixelSize(
-                        R.dimen.keyguard_smartspace_top_offset)
+                com.android.systemui.customization.R.dimen.keyguard_smartspace_top_offset)
                 * mContext.getResources().getConfiguration().fontScale
                 / mContext.getResources().getDisplayMetrics().density
                 * SMARTSPACE_TOP_PADDING_MULTIPLIER);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
index b070068..08b3e99 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -72,7 +72,7 @@
         // Request LockSettingsService to return the Gatekeeper Password in the
         // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
         // Gatekeeper Password and operationId.
-        var effectiveUserId = request.userInfo.userIdForPasswordEntry
+        var effectiveUserId = request.userInfo.deviceCredentialOwnerId
         val response =
             if (Flags.privateSpaceBp() && effectiveUserId != request.userInfo.userId) {
                 effectiveUserId = request.userInfo.userId
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 46f5c05..914fdd2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -18,34 +18,23 @@
 package com.android.systemui.keyguard.ui.binder
 
 import android.content.Context
-import android.util.DisplayMetrics
 import android.view.View
 import android.view.View.INVISIBLE
 import android.view.View.VISIBLE
 import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.internal.policy.SystemBarUtils
-import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
 import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
-import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection.Companion.getDimen
 import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.res.R
 import com.android.systemui.shared.clocks.ClockRegistry
-import com.android.systemui.util.Utils
 import kotlin.reflect.KSuspendFunction1
 
 /** Binder for the small clock view, large clock view. */
@@ -131,78 +120,6 @@
         }
     }
 
-    private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) {
-        constraints.apply {
-            constrainWidth(customR.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
-            // The following two lines make lockscreen_clock_view_large is constrained to available
-            // height when it goes beyond constraints; otherwise, it use WRAP_CONTENT
-            constrainHeight(customR.id.lockscreen_clock_view_large, WRAP_CONTENT)
-            constrainMaxHeight(customR.id.lockscreen_clock_view_large, 0)
-            val largeClockTopMargin =
-                SystemBarUtils.getStatusBarHeight(context) +
-                    context.resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) +
-                    context.resources.getDimensionPixelSize(
-                        R.dimen.keyguard_smartspace_top_offset
-                    ) +
-                    getDimen(context, DATE_WEATHER_VIEW_HEIGHT) +
-                    getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
-            connect(
-                customR.id.lockscreen_clock_view_large,
-                TOP,
-                PARENT_ID,
-                TOP,
-                largeClockTopMargin,
-            )
-            connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START)
-            connect(
-                customR.id.lockscreen_clock_view_large,
-                ConstraintSet.END,
-                PARENT_ID,
-                ConstraintSet.END,
-            )
-
-            // In preview, we'll show UDFPS icon for UDFPS devices and nothing for non-UDFPS
-            // devices, but we need position of device entry icon to constrain clock
-            if (getConstraint(lockId) != null) {
-                connect(customR.id.lockscreen_clock_view_large, BOTTOM, lockId, TOP)
-            } else {
-                // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
-                val bottomPaddingPx =
-                    context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
-                val defaultDensity =
-                    DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
-                        DisplayMetrics.DENSITY_DEFAULT.toFloat()
-                val lockIconRadiusPx = (defaultDensity * 36).toInt()
-                val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
-                connect(
-                    customR.id.lockscreen_clock_view_large,
-                    BOTTOM,
-                    PARENT_ID,
-                    BOTTOM,
-                    clockBottomMargin,
-                )
-            }
-
-            constrainWidth(customR.id.lockscreen_clock_view, WRAP_CONTENT)
-            constrainHeight(
-                customR.id.lockscreen_clock_view,
-                context.resources.getDimensionPixelSize(customR.dimen.small_clock_height),
-            )
-            connect(
-                customR.id.lockscreen_clock_view,
-                START,
-                PARENT_ID,
-                START,
-                context.resources.getDimensionPixelSize(customR.dimen.clock_padding_start) +
-                    context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
-            )
-            val smallClockTopMargin =
-                context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
-                    Utils.getStatusBarHeaderHeightKeyguard(context)
-            connect(customR.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
-        }
-    }
-
     private fun applyPreviewConstraints(
         context: Context,
         rootView: ConstraintLayout,
@@ -210,9 +127,8 @@
         viewModel: KeyguardPreviewClockViewModel,
     ) {
         val cs = ConstraintSet().apply { clone(rootView) }
-        applyClockDefaultConstraints(context, cs)
-        previewClock.largeClock.layout.applyPreviewConstraints(cs)
-        previewClock.smallClock.layout.applyPreviewConstraints(cs)
+        previewClock.largeClock.layout.applyPreviewConstraints(context, cs)
+        previewClock.smallClock.layout.applyPreviewConstraints(context, cs)
 
         // When selectedClockSize is the initial value, make both clocks invisible to avoid
         // flickering
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index ee4f41d..6096cf7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -186,12 +186,23 @@
         constraints.apply {
             connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START)
             connect(customR.id.lockscreen_clock_view_large, END, guideline, END)
-            connect(customR.id.lockscreen_clock_view_large, BOTTOM, R.id.device_entry_icon_view, TOP)
+            connect(
+                customR.id.lockscreen_clock_view_large,
+                BOTTOM,
+                R.id.device_entry_icon_view,
+                TOP,
+            )
             val largeClockTopMargin =
                 keyguardClockViewModel.getLargeClockTopMargin() +
                     getDimen(DATE_WEATHER_VIEW_HEIGHT) +
                     getDimen(ENHANCED_SMARTSPACE_HEIGHT)
-            connect(customR.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
+            connect(
+                customR.id.lockscreen_clock_view_large,
+                TOP,
+                PARENT_ID,
+                TOP,
+                largeClockTopMargin,
+            )
             constrainWidth(customR.id.lockscreen_clock_view_large, WRAP_CONTENT)
 
             // The following two lines make lockscreen_clock_view_large is constrained to available
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 5c79c0b..82adced 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -181,7 +181,7 @@
     fun getLargeClockTopMargin(): Int {
         return systemBarUtils.getStatusBarHeight() +
             resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) +
-            resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+            resources.getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset)
     }
 
     val largeClockTopMargin: Flow<Int> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
index 6579ea1..65c0f57 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
 import com.android.systemui.res.R
@@ -39,20 +40,16 @@
     val selectedClockSize: StateFlow<ClockSizeSetting> = interactor.selectedClockSize
 
     val shouldHideSmartspace: Flow<Boolean> =
-        combine(
-                interactor.selectedClockSize,
-                interactor.currentClockId,
-                ::Pair,
-            )
-            .map { (size, currentClockId) ->
-                when (size) {
-                    // TODO (b/284122375) This is temporary. We should use clockController
-                    //      .largeClock.config.hasCustomWeatherDataDisplay instead, but
-                    //      ClockRegistry.createCurrentClock is not reliable.
-                    ClockSizeSetting.DYNAMIC -> currentClockId == "DIGITAL_CLOCK_WEATHER"
-                    ClockSizeSetting.SMALL -> false
-                }
+        combine(interactor.selectedClockSize, interactor.currentClockId, ::Pair).map {
+            (size, currentClockId) ->
+            when (size) {
+                // TODO (b/284122375) This is temporary. We should use clockController
+                //      .largeClock.config.hasCustomWeatherDataDisplay instead, but
+                //      ClockRegistry.createCurrentClock is not reliable.
+                ClockSizeSetting.DYNAMIC -> currentClockId == "DIGITAL_CLOCK_WEATHER"
+                ClockSizeSetting.SMALL -> false
             }
+        }
 
     fun getSmartspaceStartPadding(context: Context): Int {
         return KeyguardSmartspaceViewModel.getSmartspaceStartMargin(context)
@@ -83,7 +80,7 @@
             } else {
                 getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
                     SystemBarUtils.getStatusBarHeight(context) +
-                    getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+                    getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 850e943..ef6ae0d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -17,8 +17,10 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.content.res.Resources
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.customization.R as customR
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
@@ -42,7 +44,6 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 class LockscreenContentViewModel
 @AssistedInject
@@ -82,10 +83,7 @@
                         unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
                         unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
                     ) { start, end ->
-                        UnfoldTranslations(
-                            start = start,
-                            end = end,
-                        )
+                        UnfoldTranslations(start = start, end = end)
                     }
                     .collect { _unfoldTranslations.value = it }
             }
@@ -102,17 +100,15 @@
 
     /** Returns a flow that indicates whether lockscreen notifications should be rendered. */
     fun areNotificationsVisible(): Flow<Boolean> {
-        return combine(
-            clockSize,
-            shadeInteractor.isShadeLayoutWide,
-        ) { clockSize, isShadeLayoutWide ->
+        return combine(clockSize, shadeInteractor.isShadeLayoutWide) { clockSize, isShadeLayoutWide
+            ->
             clockSize == ClockSize.SMALL || isShadeLayoutWide
         }
     }
 
     fun getSmartSpacePaddingTop(resources: Resources): Int {
         return if (clockSize.value == ClockSize.LARGE) {
-            resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
+            resources.getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset) +
                 resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
         } else {
             0
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
index 9580016..1f8d365 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
@@ -102,6 +102,10 @@
                 return --lives <= 0
             }
         }
+
+        override fun toString(): String {
+            return "$key = $value"
+        }
     }
 
     /**
@@ -174,7 +178,10 @@
 
         pw.println("$TAG(retainCount = $retainCount, purgeTimeoutMillis = $purgeTimeoutMillis)")
         pw.withIncreasedIndent {
-            pw.printCollection("keys present in cache", cache.keys.stream().sorted().toList())
+            pw.printCollection(
+                "entries present in cache",
+                cache.values.stream().map { it.toString() }.sorted().toList(),
+            )
 
             val misses = misses.get()
             val hits = hits.get()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 48106de..fc318d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -2395,7 +2395,8 @@
 
         FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
         mBubbleController.registerBubbleStateListener(bubbleStateListener);
-        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT);
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+                BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW);
         assertThat(bubbleStateListener.mLastUpdate).isNotNull();
         assertThat(bubbleStateListener.mLastUpdate.bubbleBarLocation).isEqualTo(
                 BubbleBarLocation.LEFT);
@@ -2408,7 +2409,8 @@
 
         FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
         mBubbleController.registerBubbleStateListener(bubbleStateListener);
-        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT);
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+                BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW);
         assertThat(bubbleStateListener.mStateChangeCalls).isEqualTo(0);
     }
 
@@ -2535,6 +2537,78 @@
 
     @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
     @Test
+    public void testEventLogging_bubbleBar_dragBarLeft() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        mPositioner.setBubbleBarLocation(BubbleBarLocation.RIGHT);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        mEntryListener.onEntryAdded(mRow);
+        assertBarMode();
+
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+                BubbleBarLocation.UpdateSource.DRAG_BAR);
+
+        verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR);
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void testEventLogging_bubbleBar_dragBarRight() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        mEntryListener.onEntryAdded(mRow);
+        assertBarMode();
+
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.RIGHT,
+                BubbleBarLocation.UpdateSource.DRAG_BAR);
+
+        verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR);
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void testEventLogging_bubbleBar_dragBubbleLeft() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        mPositioner.setBubbleBarLocation(BubbleBarLocation.RIGHT);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        mEntryListener.onEntryAdded(mRow);
+        assertBarMode();
+
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+                BubbleBarLocation.UpdateSource.DRAG_BUBBLE);
+
+        verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE);
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void testEventLogging_bubbleBar_dragBubbleRight() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        mEntryListener.onEntryAdded(mRow);
+        assertBarMode();
+
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.RIGHT,
+                BubbleBarLocation.UpdateSource.DRAG_BUBBLE);
+
+        verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE);
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
     public void testEventLogging_bubbleBar_expandAndCollapse() {
         mBubbleProperties.mIsBubbleBarEnabled = true;
         mPositioner.setIsLargeScreen(true);
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 23cee9d..1588e04 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -53,6 +53,7 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -400,9 +401,18 @@
             Slog.w(TAG, "Tombstone too large to add to DropBox: " + tombstone.toPath());
             return;
         }
-        // Read the proto tombstone file as bytes.
-        final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
 
+        // Read the proto tombstone file as bytes.
+        // Previously used Files.readAllBytes() which internally creates a ThreadLocal BufferCache
+        // via ChannelInputStream that isn't properly released. Switched to
+        // FileInputStream.transferTo() which avoids the NIO channels completely,
+        // preventing the memory leak while maintaining the same functionality.
+        final byte[] tombstoneBytes;
+        try (FileInputStream fis = new FileInputStream(tombstone);
+                ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            fis.transferTo(baos);
+            tombstoneBytes = baos.toByteArray();
+        }
         final File tombstoneProtoWithHeaders = File.createTempFile(
                 tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
         Files.setPosixFilePermissions(
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 116aeea..38df10a 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -44,7 +44,6 @@
 import android.app.BroadcastOptions.DeliveryGroupPolicy;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
-import android.compat.annotation.Overridable;
 import android.content.ComponentName;
 import android.content.IIntentReceiver;
 import android.content.Intent;
@@ -88,7 +87,6 @@
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE)
-    @Overridable
     @VisibleForTesting
     static final long CHANGE_LIMIT_PRIORITY_SCOPE = 371307720L;
 
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index f4a931f..d2af84c 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -19,7 +19,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
 import static com.android.server.am.ActivityManagerService.checkComponentPermission;
 import static com.android.server.am.BroadcastQueue.TAG;
-import static com.android.server.am.Flags.usePermissionManagerForBroadcastDeliveryCheck;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -289,33 +288,16 @@
 
         if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
                 r.requiredPermissions != null && r.requiredPermissions.length > 0) {
-            final AttributionSource[] attributionSources;
-            if (usePermissionManagerForBroadcastDeliveryCheck()) {
-                attributionSources = createAttributionSourcesForResolveInfo(info);
-            } else {
-                attributionSources = null;
-            }
+            final AttributionSource[] attributionSources =
+                    createAttributionSourcesForResolveInfo(info);
             for (int i = 0; i < r.requiredPermissions.length; i++) {
                 String requiredPermission = r.requiredPermissions[i];
-                try {
-                    if (usePermissionManagerForBroadcastDeliveryCheck()) {
-                        perm = hasPermissionForDataDelivery(
-                                requiredPermission,
-                                "Broadcast delivered to " + info.activityInfo.name,
-                                attributionSources)
-                                        ? PackageManager.PERMISSION_GRANTED
-                                        : PackageManager.PERMISSION_DENIED;
-                    } else {
-                        perm = AppGlobals.getPackageManager()
-                                .checkPermission(
-                                        requiredPermission,
-                                        info.activityInfo.applicationInfo.packageName,
-                                        UserHandle
-                                                .getUserId(info.activityInfo.applicationInfo.uid));
-                    }
-                } catch (RemoteException e) {
-                    perm = PackageManager.PERMISSION_DENIED;
-                }
+                perm = hasPermissionForDataDelivery(
+                        requiredPermission,
+                        "Broadcast delivered to " + info.activityInfo.name,
+                        attributionSources)
+                                ? PackageManager.PERMISSION_GRANTED
+                                : PackageManager.PERMISSION_DENIED;
                 if (perm != PackageManager.PERMISSION_GRANTED) {
                     return "Permission Denial: receiving "
                             + r.intent + " to "
@@ -324,15 +306,6 @@
                             + " due to sender " + r.callerPackage
                             + " (uid " + r.callingUid + ")";
                 }
-                if (!usePermissionManagerForBroadcastDeliveryCheck()) {
-                    int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
-                    if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) {
-                        if (!noteOpForManifestReceiver(appOp, r, info, component)) {
-                            return "Skipping delivery to " + info.activityInfo.packageName
-                                    + " due to required appop " + appOp;
-                        }
-                    }
-                }
             }
         }
         if (r.appOp != AppOpsManager.OP_NONE) {
@@ -452,35 +425,20 @@
 
         // Check that the receiver has the required permission(s) to receive this broadcast.
         if (r.requiredPermissions != null && r.requiredPermissions.length > 0) {
-            final AttributionSource attributionSource;
-            if (usePermissionManagerForBroadcastDeliveryCheck()) {
-                attributionSource =
-                        new AttributionSource.Builder(filter.receiverList.uid)
-                                .setPid(filter.receiverList.pid)
-                                .setPackageName(filter.packageName)
-                                .setAttributionTag(filter.featureId)
-                                .build();
-            } else {
-                attributionSource = null;
-            }
+            final AttributionSource attributionSource =
+                    new AttributionSource.Builder(filter.receiverList.uid)
+                            .setPid(filter.receiverList.pid)
+                            .setPackageName(filter.packageName)
+                            .setAttributionTag(filter.featureId)
+                            .build();
             for (int i = 0; i < r.requiredPermissions.length; i++) {
                 String requiredPermission = r.requiredPermissions[i];
-                final int perm;
-                if (usePermissionManagerForBroadcastDeliveryCheck()) {
-                    perm = hasPermissionForDataDelivery(
-                            requiredPermission,
-                            "Broadcast delivered to registered receiver " + filter.receiverId,
-                            attributionSource)
-                                    ? PackageManager.PERMISSION_GRANTED
-                                    : PackageManager.PERMISSION_DENIED;
-                } else {
-                    perm = checkComponentPermission(
-                            requiredPermission,
-                            filter.receiverList.pid,
-                            filter.receiverList.uid,
-                            -1 /* owningUid */,
-                            true /* exported */);
-                }
+                final int perm = hasPermissionForDataDelivery(
+                        requiredPermission,
+                        "Broadcast delivered to registered receiver " + filter.receiverId,
+                        attributionSource)
+                                ? PackageManager.PERMISSION_GRANTED
+                                : PackageManager.PERMISSION_DENIED;
                 if (perm != PackageManager.PERMISSION_GRANTED) {
                     return "Permission Denial: receiving "
                             + r.intent.toString()
@@ -491,24 +449,6 @@
                             + " due to sender " + r.callerPackage
                             + " (uid " + r.callingUid + ")";
                 }
-                if (!usePermissionManagerForBroadcastDeliveryCheck()) {
-                    int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
-                    if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
-                            && mService.getAppOpsManager().noteOpNoThrow(appOp,
-                            filter.receiverList.uid, filter.packageName, filter.featureId,
-                            "Broadcast delivered to registered receiver " + filter.receiverId)
-                            != AppOpsManager.MODE_ALLOWED) {
-                        return "Appop Denial: receiving "
-                                + r.intent.toString()
-                                + " to " + filter.receiverList.app
-                                + " (pid=" + filter.receiverList.pid
-                                + ", uid=" + filter.receiverList.uid + ")"
-                                + " requires appop " + AppOpsManager.permissionToOp(
-                                requiredPermission)
-                                + " due to sender " + r.callerPackage
-                                + " (uid " + r.callingUid + ")";
-                    }
-                }
             }
         }
         if ((r.requiredPermissions == null || r.requiredPermissions.length == 0)) {
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
new file mode 100644
index 0000000..745665b
--- /dev/null
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -0,0 +1,67 @@
+/*
+ * 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.pm;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.parsing.PackageLite;
+import android.os.OutcomeReceiver;
+
+import java.util.List;
+
+/**
+ * Helper class to interact with SDK Dependency Installer service.
+ */
+public class InstallDependencyHelper {
+    private final SharedLibrariesImpl mSharedLibraries;
+
+    InstallDependencyHelper(SharedLibrariesImpl sharedLibraries) {
+        mSharedLibraries = sharedLibraries;
+    }
+
+    void resolveLibraryDependenciesIfNeeded(PackageLite pkg,
+            OutcomeReceiver<Void, PackageManagerException> callback) {
+        final List<SharedLibraryInfo> missing;
+        try {
+            missing = mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
+        } catch (PackageManagerException e) {
+            callback.onError(e);
+            return;
+        }
+
+        if (missing.isEmpty()) {
+            // No need for dependency resolution. Move to installation directly.
+            callback.onResult(null);
+            return;
+        }
+
+        try {
+            bindToDependencyInstaller();
+        } catch (Exception e) {
+            PackageManagerException pe = new PackageManagerException(
+                    INSTALL_FAILED_MISSING_SHARED_LIBRARY, e.getMessage());
+            callback.onError(pe);
+        }
+    }
+
+    private void bindToDependencyInstaller() {
+        throw new IllegalStateException("Failed to bind to Dependency Installer");
+    }
+
+
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ef09976..eb70748 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -220,6 +220,7 @@
 
     private AppOpsManager mAppOps;
     private final VerifierController mVerifierController;
+    private final InstallDependencyHelper mInstallDependencyHelper;
 
     private final HandlerThread mInstallThread;
     private final Handler mInstallHandler;
@@ -346,6 +347,8 @@
         synchronized (mVerificationPolicyPerUser) {
             mVerificationPolicyPerUser.put(USER_SYSTEM, DEFAULT_VERIFICATION_POLICY);
         }
+        mInstallDependencyHelper = new InstallDependencyHelper(
+                mPm.mInjector.getSharedLibrariesImpl());
 
         LocalServices.getService(SystemServiceManager.class).startService(
                 new Lifecycle(context, this));
@@ -543,7 +546,7 @@
                             session = PackageInstallerSession.readFromXml(in, mInternalCallback,
                                     mContext, mPm, mInstallThread.getLooper(), mStagingManager,
                                     mSessionsDir, this, mSilentUpdatePolicy,
-                                    mVerifierController);
+                                    mVerifierController, mInstallDependencyHelper);
                         } catch (Exception e) {
                             Slog.e(TAG, "Could not read session", e);
                             continue;
@@ -1065,7 +1068,8 @@
                 userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid,
                 null, null, false, false, false, false, null, SessionInfo.INVALID_ID,
                 false, false, false, PackageManager.INSTALL_UNKNOWN, "", null,
-                mVerifierController, verificationPolicy, verificationPolicy);
+                mVerifierController, verificationPolicy, verificationPolicy,
+                mInstallDependencyHelper);
 
         synchronized (mSessions) {
             mSessions.put(sessionId, session);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2a92de5..bad1201 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -145,6 +145,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.OutcomeReceiver;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelableException;
 import android.os.PersistableBundle;
@@ -433,6 +434,8 @@
     private final StagingManager mStagingManager;
     @NonNull private final VerifierController mVerifierController;
 
+    private final InstallDependencyHelper mInstallDependencyHelper;
+
     final int sessionId;
     final int userId;
     final SessionParams params;
@@ -1188,7 +1191,8 @@
             String sessionErrorMessage, DomainSet preVerifiedDomains,
             @NonNull VerifierController verifierController,
             @PackageInstaller.VerificationPolicy int initialVerificationPolicy,
-            @PackageInstaller.VerificationPolicy int currentVerificationPolicy) {
+            @PackageInstaller.VerificationPolicy int currentVerificationPolicy,
+            InstallDependencyHelper installDependencyHelper) {
         mCallback = callback;
         mContext = context;
         mPm = pm;
@@ -1200,6 +1204,7 @@
         mVerifierController = verifierController;
         mInitialVerificationPolicy = initialVerificationPolicy;
         mCurrentVerificationPolicy = new AtomicInteger(currentVerificationPolicy);
+        mInstallDependencyHelper = installDependencyHelper;
 
         this.sessionId = sessionId;
         this.userId = userId;
@@ -2611,6 +2616,13 @@
         maybeFinishChildSessions(error, msg);
     }
 
+    private void onSessionDependencyResolveFailure(int error, String msg) {
+        Slog.e(TAG, "Failed to resolve dependency for session " + sessionId);
+        // Dispatch message to remove session from PackageInstallerService.
+        dispatchSessionFinished(error, msg, null);
+        maybeFinishChildSessions(error, msg);
+    }
+
     private void onSystemDataLoaderUnrecoverable() {
         final String packageName = getPackageName();
         if (TextUtils.isEmpty(packageName)) {
@@ -3402,7 +3414,34 @@
                     /* extras= */ null, /* forPreapproval= */ false);
             return;
         }
-        install();
+
+        if (Flags.sdkDependencyInstaller() && !isMultiPackage()) {
+            resolveLibraryDependenciesIfNeeded();
+        } else {
+            install();
+        }
+    }
+
+
+    private void resolveLibraryDependenciesIfNeeded() {
+        synchronized (mLock) {
+            // TODO(b/372862145): Callback should be called on a handler passed as parameter
+            mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(mPackageLite,
+                    new OutcomeReceiver<>() {
+
+                        @Override
+                        public void onResult(Void result) {
+                            install();
+                        }
+
+                        @Override
+                        public void onError(@NonNull PackageManagerException e) {
+                            final String completeMsg = ExceptionUtils.getCompleteMessage(e);
+                            setSessionFailed(e.error, completeMsg);
+                            onSessionDependencyResolveFailure(e.error, completeMsg);
+                        }
+                    });
+        }
     }
 
     /**
@@ -6048,7 +6087,8 @@
             @NonNull StagingManager stagingManager, @NonNull File sessionsDir,
             @NonNull PackageSessionProvider sessionProvider,
             @NonNull SilentUpdatePolicy silentUpdatePolicy,
-            @NonNull VerifierController verifierController)
+            @NonNull VerifierController verifierController,
+            @NonNull InstallDependencyHelper installDependencyHelper)
             throws IOException, XmlPullParserException {
         final int sessionId = in.getAttributeInt(null, ATTR_SESSION_ID);
         final int userId = in.getAttributeInt(null, ATTR_USER_ID);
@@ -6257,6 +6297,6 @@
                 stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed,
                 childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
                 sessionErrorCode, sessionErrorMessage, preVerifiedDomains, verifierController,
-                initialVerificationPolicy, currentVerificationPolicy);
+                initialVerificationPolicy, currentVerificationPolicy, installDependencyHelper);
     }
 }
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 929fccc..fc54f68 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -33,6 +33,7 @@
 import android.content.pm.Signature;
 import android.content.pm.SigningDetails;
 import android.content.pm.VersionedPackage;
+import android.content.pm.parsing.PackageLite;
 import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
@@ -83,6 +84,7 @@
     private static final boolean DEBUG_SHARED_LIBRARIES = false;
 
     private static final String LIBRARY_TYPE_SDK = "sdk";
+    private static final String LIBRARY_TYPE_STATIC = "static shared";
 
     /**
      * Apps targeting Android S and above need to declare dependencies to the public native
@@ -926,18 +928,19 @@
         if (!pkg.getUsesLibraries().isEmpty()) {
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, null,
                     pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null,
-                    availablePackages, newLibraries);
+                    availablePackages, newLibraries, null);
         }
         if (!pkg.getUsesStaticLibraries().isEmpty()) {
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
                     pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
-                    null, pkg.getPackageName(), "static shared", true,
-                    pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries);
+                    null, pkg.getPackageName(), LIBRARY_TYPE_STATIC, true,
+                    pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries,
+                    null);
         }
         if (!pkg.getUsesOptionalLibraries().isEmpty()) {
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null,
                     null, pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
-                    usesLibraryInfos, availablePackages, newLibraries);
+                    usesLibraryInfos, availablePackages, newLibraries, null);
         }
         if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
                 pkg.getPackageName(), pkg.getTargetSdkVersion())) {
@@ -945,13 +948,13 @@
                 usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
                         null, null, pkg.getPackageName(), "native shared", true,
                         pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
-                        newLibraries);
+                        newLibraries, null);
             }
             if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
                 usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
                         null, null, null, pkg.getPackageName(), "native shared", false,
                         pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
-                        newLibraries);
+                        newLibraries, null);
             }
         }
         if (!pkg.getUsesSdkLibraries().isEmpty()) {
@@ -961,11 +964,34 @@
                     pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
                     pkg.getUsesSdkLibrariesOptional(),
                     pkg.getPackageName(), LIBRARY_TYPE_SDK, required, pkg.getTargetSdkVersion(),
-                    usesLibraryInfos, availablePackages, newLibraries);
+                    usesLibraryInfos, availablePackages, newLibraries, null);
         }
         return usesLibraryInfos;
     }
 
+    List<SharedLibraryInfo> collectMissingSharedLibraryInfos(PackageLite pkgLite)
+            throws PackageManagerException {
+        ArrayList<SharedLibraryInfo> missingSharedLibrary = new ArrayList<>();
+        synchronized (mPm.mLock) {
+            collectSharedLibraryInfos(pkgLite.getUsesSdkLibraries(),
+                    pkgLite.getUsesSdkLibrariesVersionsMajor(),
+                    pkgLite.getUsesSdkLibrariesCertDigests(),
+                    /*libsOptional=*/ null, pkgLite.getPackageName(), LIBRARY_TYPE_SDK,
+                    /*required=*/ true, pkgLite.getTargetSdk(),
+                    /*outUsedLibraries=*/ null, mPm.mPackages, /*newLibraries=*/ null,
+                    missingSharedLibrary);
+
+            collectSharedLibraryInfos(pkgLite.getUsesStaticLibraries(),
+                    pkgLite.getUsesStaticLibrariesVersions(),
+                    pkgLite.getUsesStaticLibrariesCertDigests(),
+                    /*libsOptional=*/ null, pkgLite.getPackageName(), LIBRARY_TYPE_STATIC,
+                    /*required=*/ true, pkgLite.getTargetSdk(),
+                    /*outUsedLibraries=*/ null, mPm.mPackages, /*newLibraries=*/ null,
+                    missingSharedLibrary);
+        }
+        return missingSharedLibrary;
+    }
+
     private ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
             @NonNull List<String> requestedLibraries,
             @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
@@ -973,7 +999,8 @@
             @NonNull String packageName, @NonNull String libraryType, boolean required,
             int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
             @NonNull final Map<String, AndroidPackage> availablePackages,
-            @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
+            @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries,
+            @Nullable final List<SharedLibraryInfo> outMissingSharedLibraryInfos)
             throws PackageManagerException {
         final int libCount = requestedLibraries.size();
         for (int i = 0; i < libCount; i++) {
@@ -986,16 +1013,33 @@
                         libName, libVersion, mSharedLibraries, newLibraries);
             }
             if (libraryInfo == null) {
-                // Only allow app be installed if the app specifies the sdk-library dependency is
-                // optional
-                if (required || (LIBRARY_TYPE_SDK.equals(libraryType) && (libsOptional != null
-                        && !libsOptional[i]))) {
-                    throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
-                            "Package " + packageName + " requires unavailable " + libraryType
-                                    + " library " + libName + "; failing!");
-                } else if (DEBUG_SHARED_LIBRARIES) {
-                    Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType
-                            + " library " + libName + "; ignoring!");
+                if (required) {
+                    boolean isSdkOrStatic = libraryType.equals(LIBRARY_TYPE_SDK)
+                            || libraryType.equals(LIBRARY_TYPE_STATIC);
+                    if (isSdkOrStatic && outMissingSharedLibraryInfos != null) {
+                        // TODO(b/372862145): Pass the CertDigest too
+                        // If Dependency Installation is supported, try that instead of failing.
+                        SharedLibraryInfo missingLibrary = new SharedLibraryInfo(
+                                libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE
+                        );
+                        outMissingSharedLibraryInfos.add(missingLibrary);
+                    } else {
+                        throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+                                "Package " + packageName + " requires unavailable " + libraryType
+                                + " library " + libName + "; failing!");
+                    }
+                } else {
+                    // Only allow app be installed if the app specifies the sdk-library
+                    // dependency is optional
+                    boolean isOptional = libsOptional != null && libsOptional[i];
+                    if (LIBRARY_TYPE_SDK.equals(libraryType) && !isOptional) {
+                        throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+                                "Package " + packageName + " requires unavailable " + libraryType
+                                + " library " + libName + "; failing!");
+                    } else if (DEBUG_SHARED_LIBRARIES) {
+                        Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType
+                                + " library " + libName + "; ignoring!");
+                    }
                 }
             } else {
                 if (requiredVersions != null && requiredCertDigests != null) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9759772..19b0343 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1621,7 +1621,8 @@
             mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
             t.traceEnd();
 
-            if (!isWatch && android.app.supervision.flags.Flags.supervisionApi()) {
+            if (android.app.supervision.flags.Flags.supervisionApi()
+                    && (!isWatch || android.app.supervision.flags.Flags.supervisionApiOnWear())) {
                 t.traceBegin("StartSupervisionService");
                 mSystemServiceManager.startService(SupervisionService.Lifecycle.class);
                 t.traceEnd();
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
index 09d0e4a..5a59c57 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
@@ -201,7 +201,8 @@
             /* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar")),
             /* VerifierController */ mock(VerifierController::class.java),
             /* initialVerificationPolicy */ VERIFICATION_POLICY_BLOCK_FAIL_OPEN,
-            /* currentVerificationPolicy */ VERIFICATION_POLICY_BLOCK_FAIL_CLOSED
+            /* currentVerificationPolicy */ VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
+            /* installDependencyHelper */ null
         )
     }
 
@@ -256,7 +257,8 @@
                                 mTmpDir,
                                 mock(PackageSessionProvider::class.java),
                                 mock(SilentUpdatePolicy::class.java),
-                                mock(VerifierController::class.java)
+                                mock(VerifierController::class.java),
+                                mock(InstallDependencyHelper::class.java)
                             )
                             ret.add(session)
                         } catch (e: Exception) {
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 979384c6..6acf242 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -109,6 +109,10 @@
     optimize: {
         enabled: false,
     },
+
+    data: [
+        ":HelloWorldUsingSdk1And2",
+    ],
 }
 
 java_library {
diff --git a/services/tests/mockingservicestests/AndroidTest.xml b/services/tests/mockingservicestests/AndroidTest.xml
index 7782d57..2b90119 100644
--- a/services/tests/mockingservicestests/AndroidTest.xml
+++ b/services/tests/mockingservicestests/AndroidTest.xml
@@ -23,6 +23,12 @@
         <option name="test-file-name" value="FrameworksMockingServicesTests.apk" />
     </target_preparer>
 
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true"/>
+        <option name="push-file" key="HelloWorldUsingSdk1And2.apk"
+            value="/data/local/tmp/tests/smockingservicestest/pm/HelloWorldUsingSdk1And2.apk"/>
+    </target_preparer>
+
     <option name="test-tag" value="FrameworksMockingServicesTests" />
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
new file mode 100644
index 0000000..f6c644e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.pm;
+
+import static android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.parsing.ApkLite;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.os.FileUtils;
+import android.os.OutcomeReceiver;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+@RunWith(JUnit4.class)
+@RequiresFlagsEnabled(FLAG_SDK_DEPENDENCY_INSTALLER)
+public class InstallDependencyHelperTest {
+
+    @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
+    @Rule public final CheckFlagsRule checkFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    private static final String PUSH_FILE_DIR = "/data/local/tmp/tests/smockingservicestest/pm/";
+    private static final String TEST_APP_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk";
+
+    @Mock private SharedLibrariesImpl mSharedLibraries;
+    private InstallDependencyHelper mInstallDependencyHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mInstallDependencyHelper = new InstallDependencyHelper(mSharedLibraries);
+    }
+
+    @Test
+    public void testResolveLibraryDependenciesIfNeeded_errorInSharedLibrariesImpl()
+            throws Exception {
+        doThrow(new PackageManagerException(new Exception("xyz")))
+                .when(mSharedLibraries).collectMissingSharedLibraryInfos(any());
+
+        PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+        CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
+        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+        callback.assertFailure();
+
+        assertThat(callback.error).hasMessageThat().contains("xyz");
+    }
+
+    @Test
+    public void testResolveLibraryDependenciesIfNeeded_failsToBind() throws Exception {
+        // Return a non-empty list as missing dependency
+        PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+        List<SharedLibraryInfo> missingDependency = Collections.singletonList(
+                mock(SharedLibraryInfo.class));
+        when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg)))
+                .thenReturn(missingDependency);
+
+        CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
+        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+        callback.assertFailure();
+
+        assertThat(callback.error).hasMessageThat().contains(
+                "Failed to bind to Dependency Installer");
+    }
+
+
+    @Test
+    public void testResolveLibraryDependenciesIfNeeded_allDependenciesInstalled() throws Exception {
+        // Return an empty list as missing dependency
+        PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+        List<SharedLibraryInfo> missingDependency = Collections.emptyList();
+        when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg)))
+                .thenReturn(missingDependency);
+
+        CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ true);
+        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+        callback.assertSuccess();
+    }
+
+    private static class CallbackHelper implements OutcomeReceiver<Void, PackageManagerException> {
+        public PackageManagerException error;
+
+        private final CountDownLatch mWait = new CountDownLatch(1);
+        private final boolean mExpectSuccess;
+
+        CallbackHelper(boolean expectSuccess) {
+            mExpectSuccess = expectSuccess;
+        }
+
+        @Override
+        public void onResult(Void result) {
+            if (!mExpectSuccess) {
+                fail("Expected to fail");
+            }
+            mWait.countDown();
+        }
+
+        @Override
+        public void onError(@NonNull PackageManagerException e) {
+            if (mExpectSuccess) {
+                fail("Expected success but received: " + e);
+            }
+            error = e;
+            mWait.countDown();
+        }
+
+        void assertSuccess() throws Exception {
+            assertThat(mWait.await(1000, TimeUnit.MILLISECONDS)).isTrue();
+            assertThat(error).isNull();
+        }
+
+        void assertFailure() throws Exception {
+            assertThat(mWait.await(1000, TimeUnit.MILLISECONDS)).isTrue();
+            assertThat(error).isNotNull();
+        }
+
+    }
+
+    private PackageLite getPackageLite(String apkFileName) throws Exception {
+        File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK2);
+        ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(
+                ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+        assertThat(result.isError()).isFalse();
+        ApkLite baseApk = result.getResult();
+
+        return new PackageLite(/*path=*/ null, baseApk.getPath(), baseApk,
+                /*splitNames=*/ null, /*isFeatureSplits=*/ null, /*usesSplitNames=*/ null,
+                /*configForSplit=*/ null, /*splitApkPaths=*/ null,
+                /*splitRevisionCodes=*/ null, baseApk.getTargetSdkVersion(),
+                /*requiredSplitTypes=*/ null, /*splitTypes=*/ null);
+    }
+
+    private File copyApkToTmpDir(String apkFileName) throws Exception {
+        File outFile = temporaryFolder.newFile(apkFileName);
+        String apkFilePath = PUSH_FILE_DIR + apkFileName;
+        File apkFile = new File(apkFilePath);
+        assertThat(apkFile.exists()).isTrue();
+        try (InputStream is = new FileInputStream(apkFile)) {
+            FileUtils.copyToFileOrThrow(is, outFile);
+        }
+        return outFile;
+    }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index 591e8df..71c60ad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -742,10 +742,11 @@
                 /* stagedSessionErrorMessage */ "no error",
                 /* preVerifiedDomains */ null,
                 /* verifierController */ null,
-                /* initialVerificationPolicy */ 
+                /* initialVerificationPolicy */
                 PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
                 /* currentVerificationPolicy */
-                PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED);
+                PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
+                /* installDependencyHelper */ null);
 
         StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
         doReturn(packageName).when(stagedSession).getPackageName();
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index ac96ef2..be5c84c 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -53,7 +53,6 @@
     private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
     private static final Field MESSAGE_NEXT_FIELD;
     private static final Field MESSAGE_WHEN_FIELD;
-    private static Field MESSAGE_QUEUE_USE_CONCURRENT_FIELD = null;
 
     private Looper mLooper;
     private MessageQueue mQueue;
@@ -64,14 +63,6 @@
 
     static {
         try {
-            MESSAGE_QUEUE_USE_CONCURRENT_FIELD =
-                    MessageQueue.class.getDeclaredField("mUseConcurrent");
-            MESSAGE_QUEUE_USE_CONCURRENT_FIELD.setAccessible(true);
-        } catch (NoSuchFieldException ignored) {
-            // Ignore - maybe this is not CombinedMessageQueue?
-        }
-
-        try {
             MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
             MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
             MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
@@ -155,15 +146,6 @@
         mLooper = l;
         mQueue = mLooper.getQueue();
         mHandler = new Handler(mLooper);
-
-        // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing.
-        if (MESSAGE_QUEUE_USE_CONCURRENT_FIELD != null) {
-            try {
-                MESSAGE_QUEUE_USE_CONCURRENT_FIELD.set(mQueue, false);
-            } catch (IllegalAccessException e) {
-                throw new RuntimeException(e);
-            }
-        }
     }
 
     /**
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index 1bcfaf6..56b0a25 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -100,18 +100,6 @@
             throw new RuntimeException("Reflection error constructing or accessing looper", e);
         }
 
-        // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing.
-        try {
-            Field messageQueueUseConcurrentField =
-                    MessageQueue.class.getDeclaredField("mUseConcurrent");
-            messageQueueUseConcurrentField.setAccessible(true);
-            messageQueueUseConcurrentField.set(mLooper.getQueue(), false);
-        } catch (NoSuchFieldException e) {
-            // Ignore - maybe this is not CombinedMessageQueue?
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException("Reflection error constructing or accessing looper", e);
-        }
-
         mClock = clock;
     }