Merge "Expose binder for sensitive content protection" into main
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 7c4df28..9c1a8e8 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -135,7 +135,7 @@
 
   @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public class SignedPackage {
     method @NonNull public byte[] getCertificateDigest();
-    method @NonNull public String getPkgName();
+    method @NonNull public String getPackageName();
   }
 
 }
diff --git a/core/java/android/content/pm/SignedPackage.java b/core/java/android/content/pm/SignedPackage.java
index 4d1b136..7bffa77 100644
--- a/core/java/android/content/pm/SignedPackage.java
+++ b/core/java/android/content/pm/SignedPackage.java
@@ -35,9 +35,9 @@
     private final SignedPackageParcel mData;
 
     /** @hide */
-    public SignedPackage(@NonNull String pkgName, @NonNull byte[] certificateDigest) {
+    public SignedPackage(@NonNull String packageName, @NonNull byte[] certificateDigest) {
         SignedPackageParcel data = new SignedPackageParcel();
-        data.pkgName = pkgName;
+        data.packageName = packageName;
         data.certificateDigest = certificateDigest;
         mData = data;
     }
@@ -52,8 +52,8 @@
         return mData;
     }
 
-    public @NonNull String getPkgName() {
-        return mData.pkgName;
+    public @NonNull String getPackageName() {
+        return mData.packageName;
     }
 
     public @NonNull byte[] getCertificateDigest() {
@@ -64,12 +64,12 @@
     public boolean equals(Object o) {
         if (this == o) return true;
         if (!(o instanceof SignedPackage that)) return false;
-        return mData.pkgName.equals(that.mData.pkgName) && Arrays.equals(mData.certificateDigest,
-                that.mData.certificateDigest);
+        return mData.packageName.equals(that.mData.packageName) && Arrays.equals(
+                mData.certificateDigest, that.mData.certificateDigest);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mData.pkgName, Arrays.hashCode(mData.certificateDigest));
+        return Objects.hash(mData.packageName, Arrays.hashCode(mData.certificateDigest));
     }
 }
diff --git a/core/java/android/content/pm/SignedPackageParcel.aidl b/core/java/android/content/pm/SignedPackageParcel.aidl
index 7957f7f..bb4dc86 100644
--- a/core/java/android/content/pm/SignedPackageParcel.aidl
+++ b/core/java/android/content/pm/SignedPackageParcel.aidl
@@ -20,6 +20,6 @@
 
 /** @hide */
 parcelable SignedPackageParcel {
-    String pkgName;
+    String packageName;
     byte[] certificateDigest;
 }
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 5684929..1513e9a 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -144,6 +144,13 @@
 }
 
 flag {
+    name: "show_set_screen_lock_dialog"
+    namespace: "profile_experiences"
+    description: "Display the dialog to set up screen lock when private space unlock operation is requested"
+    bug: "316129700"
+}
+
+flag {
     name: "reorder_wallpaper_during_user_switch"
     namespace: "multiuser"
     description: "Reorder loading home and lock screen wallpapers during a user switch."
@@ -162,4 +169,4 @@
     namespace: "profile_experiences"
     description: "Disables adding items belonging to Private Space on Home Screen manually as well as automatically"
     bug: "287975131"
-}
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
new file mode 100644
index 0000000..93fe37c
--- /dev/null
+++ b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.hardware.biometrics.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
+import static android.provider.Settings.ACTION_BIOMETRIC_ENROLL;
+import static android.provider.Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
+import android.app.AlertDialog;
+import android.app.KeyguardManager;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A dialog shown to the user that prompts them to set the screen lock for the current foreground
+ * user. Should be called from the context of foreground user.
+ */
+public class SetScreenLockDialogActivity extends AlertActivity
+        implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+    private static final String TAG = "SetScreenLockDialog";
+    public static final String EXTRA_LAUNCH_REASON = "launch_reason";
+    /**
+     * User id associated with the workflow that wants to launch the prompt to set up the
+     * screen lock
+     */
+    public static final String EXTRA_ORIGIN_USER_ID = "origin_user_id";
+    private static final String PACKAGE_NAME = "android";
+    @IntDef(prefix = "LAUNCH_REASON_", value = {
+            LAUNCH_REASON_PRIVATE_SPACE_SETTINGS_ACCESS,
+            LAUNCH_REASON_DISABLE_QUIET_MODE,
+            LAUNCH_REASON_UNKNOWN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LaunchReason {
+    }
+    public static final int LAUNCH_REASON_UNKNOWN = -1;
+    public static final int LAUNCH_REASON_DISABLE_QUIET_MODE = 1;
+    public static final int LAUNCH_REASON_PRIVATE_SPACE_SETTINGS_ACCESS = 2;
+    private @LaunchReason int mReason;
+    private int mOriginUserId;
+
+    @Override
+    @RequiresPermission(HIDE_OVERLAY_WINDOWS)
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (!(android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.showSetScreenLockDialog())) {
+            finish();
+            return;
+        }
+        Intent intent = getIntent();
+        mReason = intent.getIntExtra(EXTRA_LAUNCH_REASON, LAUNCH_REASON_UNKNOWN);
+        mOriginUserId = intent.getIntExtra(EXTRA_ORIGIN_USER_ID, UserHandle.USER_NULL);
+
+        if (mReason == LAUNCH_REASON_UNKNOWN) {
+            Log.e(TAG, "Invalid launch reason: " + mReason);
+            finish();
+            return;
+        }
+
+        final KeyguardManager km = getSystemService(KeyguardManager.class);
+        if (km == null) {
+            Log.e(TAG, "Error fetching keyguard manager");
+            return;
+        }
+        if (km.isDeviceSecure()) {
+            Log.w(TAG, "Closing the activity since screen lock is already set");
+            return;
+        }
+
+        Log.d(TAG, "Launching screen lock setup dialog due to " + mReason);
+        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setTitle(R.string.set_up_screen_lock_title)
+                .setOnDismissListener(this)
+                .setPositiveButton(R.string.set_up_screen_lock_action_label, this)
+                .setNegativeButton(R.string.cancel, this);
+        setLaunchUserSpecificMessage(builder);
+        final AlertDialog dialog = builder.create();
+        dialog.create();
+        getWindow().setHideOverlayWindows(true);
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+        dialog.show();
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        finish();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == DialogInterface.BUTTON_POSITIVE) {
+            Intent setNewLockIntent = new Intent(ACTION_BIOMETRIC_ENROLL);
+            setNewLockIntent.putExtra(EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, DEVICE_CREDENTIAL);
+            startActivity(setNewLockIntent);
+        } else {
+            finish();
+        }
+    }
+
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
+    private void setLaunchUserSpecificMessage(AlertDialog.Builder builder) {
+        if (mReason == LAUNCH_REASON_PRIVATE_SPACE_SETTINGS_ACCESS) {
+            // Always set private space message if launch reason is specific to private space
+            builder.setMessage(R.string.private_space_set_up_screen_lock_message);
+            return;
+        }
+        final UserManager userManager = getApplicationContext().getSystemService(UserManager.class);
+        if (userManager != null) {
+            UserInfo userInfo = userManager.getUserInfo(mOriginUserId);
+            if (userInfo != null && userInfo.isPrivateProfile()) {
+                builder.setMessage(R.string.private_space_set_up_screen_lock_message);
+            }
+        }
+    }
+
+    /** Returns a basic intent to display the screen lock dialog */
+    public static Intent createBaseIntent(@LaunchReason int launchReason) {
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName(PACKAGE_NAME,
+                SetScreenLockDialogActivity.class.getName()));
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        intent.putExtra(EXTRA_LAUNCH_REASON, launchReason);
+        return intent;
+    }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 710b5f8..8720f94 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8370,6 +8370,12 @@
                 android:process=":ui">
         </activity>
 
+        <activity android:name="com.android.internal.app.SetScreenLockDialogActivity"
+                  android:theme="@style/Theme.Dialog.Confirmation"
+                  android:excludeFromRecents="true"
+                  android:process=":ui">
+        </activity>
+
         <activity android:name="com.android.internal.app.BlockedAppActivity"
                 android:theme="@style/Theme.Dialog.Confirmation"
                 android:excludeFromRecents="true"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e999695..59066eb 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5448,6 +5448,13 @@
     <!-- Title for button to launch the personal safety app to make an emergency call    -->
     <string name="work_mode_emergency_call_button">Emergency</string>
 
+    <!-- Title of the alert dialog prompting the user to set up a screen lock [CHAR LIMIT=30] -->
+    <string name="set_up_screen_lock_title">Set a screen lock</string>
+    <!-- Action label for the dialog prompting the user to set up a screen lock [CHAR LIMIT=30] -->
+    <string name="set_up_screen_lock_action_label">Set screen lock</string>
+    <!-- Message shown in the dialog prompting the user to set up a screen lock to access private space [CHAR LIMIT=30] -->
+    <string name="private_space_set_up_screen_lock_message">To use your private space, set a screen lock on this device</string>
+
     <!-- Title of the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=50] -->
     <string name="app_blocked_title">App is not available</string>
     <!-- Default message shown in the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cf9c02a..3284791 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3247,6 +3247,12 @@
   <java-symbol type="string" name="work_mode_off_title" />
   <java-symbol type="string" name="work_mode_turn_on" />
 
+  <!-- Alert dialog prompting the user to set up a screen lock -->
+  <java-symbol type="string" name="set_up_screen_lock_title" />
+  <java-symbol type="string" name="set_up_screen_lock_action_label" />
+  <!-- Message for the alert dialog prompting the user to set up a screen lock to access private space -->
+  <java-symbol type="string" name="private_space_set_up_screen_lock_message" />
+
   <java-symbol type="string" name="deprecated_target_sdk_message" />
   <java-symbol type="string" name="deprecated_target_sdk_app_store" />
 
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/anc/AncModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/anc/AncModule.kt
new file mode 100644
index 0000000..a4fb05d
--- /dev/null
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/anc/AncModule.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc
+
+import dagger.Module
+
+@Module interface AncModule
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
index 7d692cc..d66bada 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone
 
 import android.content.Context
+import androidx.annotation.GravityInt
 import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
@@ -45,14 +46,17 @@
  * @param context the [Context] in which the dialog will be constructed.
  * @param dismissOnDeviceLock whether the dialog should be automatically dismissed when the device
  *   is locked (true by default).
+ * @param dialogGravity is one of the [android.view.Gravity] and determines dialog position on the
+ *   screen.
  */
 fun SystemUIDialogFactory.create(
     context: Context = this.applicationContext,
     theme: Int = SystemUIDialog.DEFAULT_THEME,
     dismissOnDeviceLock: Boolean = SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
+    @GravityInt dialogGravity: Int? = null,
     content: @Composable (SystemUIDialog) -> Unit,
 ): ComponentSystemUIDialog {
-    val dialog = create(context, theme, dismissOnDeviceLock)
+    val dialog = create(context, theme, dismissOnDeviceLock, dialogGravity)
 
     // Create the dialog so that it is properly constructed before we set the Compose content.
     // Otherwise, the ComposeView won't render properly.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt
new file mode 100644
index 0000000..ccb5d36
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc
+
+import com.android.systemui.volume.panel.component.anc.domain.AncAvailabilityCriteria
+import com.android.systemui.volume.panel.component.anc.ui.composable.AncPopup
+import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel
+import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+/** Dagger module, that provides Active Noise Cancellation Volume Panel UI functionality. */
+@Module
+interface AncModule {
+
+    @Binds
+    @IntoMap
+    @StringKey(VolumePanelComponents.ANC)
+    fun bindComponentAvailabilityCriteria(
+        criteria: AncAvailabilityCriteria
+    ): ComponentAvailabilityCriteria
+
+    companion object {
+
+        @Provides
+        @IntoMap
+        @StringKey(VolumePanelComponents.ANC)
+        fun provideVolumePanelUiComponent(
+            viewModel: AncViewModel,
+            popup: AncPopup,
+        ): VolumePanelUiComponent = ButtonComponent(viewModel.button, popup::show)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
new file mode 100644
index 0000000..8ac84ff
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.ui.composable
+
+import android.content.Context
+import android.view.View
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.slice.Slice
+import androidx.slice.widget.SliceView
+import com.android.systemui.animation.Expandable
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel
+import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
+import javax.inject.Inject
+
+/** ANC popup up displaying ANC control [Slice]. */
+class AncPopup
+@Inject
+constructor(
+    private val volumePanelPopup: VolumePanelPopup,
+    private val viewModel: AncViewModel,
+) {
+
+    /** Shows a popup with the [expandable] animation. */
+    fun show(expandable: Expandable) {
+        volumePanelPopup.show(expandable, { Title() }, { Content(it) })
+    }
+
+    @Composable
+    private fun Title() {
+        Text(
+            text = stringResource(R.string.volume_panel_noise_control_title),
+            style = MaterialTheme.typography.titleMedium,
+            textAlign = TextAlign.Center,
+            maxLines = 1,
+        )
+    }
+
+    @Composable
+    private fun Content(dialog: SystemUIDialog) {
+        val slice: Slice? by viewModel.slice.collectAsState()
+
+        if (slice == null) {
+            SideEffect { dialog.dismiss() }
+            return
+        }
+
+        AndroidView<SliceView>(
+            modifier = Modifier.fillMaxWidth(),
+            factory = { context: Context ->
+                SliceView(context).apply {
+                    mode = SliceView.MODE_LARGE
+                    isScrollable = false
+                    importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+                    setShowTitleItems(true)
+                    addOnLayoutChangeListener(
+                        OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
+                    )
+                }
+            },
+            update = { sliceView: SliceView -> sliceView.slice = slice }
+        )
+    }
+
+    private class OnWidthChangedLayoutListener(private val widthChanged: (Int) -> Unit) :
+        View.OnLayoutChangeListener {
+        override fun onLayoutChange(
+            v: View?,
+            left: Int,
+            top: Int,
+            right: Int,
+            bottom: Int,
+            oldLeft: Int,
+            oldTop: Int,
+            oldRight: Int,
+            oldBottom: Int
+        ) {
+            val newWidth = right - left
+            val oldWidth = oldRight - oldLeft
+            if (oldWidth != newWidth) {
+                widthChanged(newWidth)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
index 0cf4367..d401261 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
@@ -47,7 +47,7 @@
     @Composable
     override fun VolumePanelComposeScope.Content(modifier: Modifier) {
         Row(
-            modifier = modifier.height(48.dp).fillMaxWidth(),
+            modifier = modifier.height(if (isLargeScreen) 54.dp else 48.dp).fillMaxWidth(),
             horizontalArrangement = Arrangement.SpaceBetween,
             verticalAlignment = Alignment.CenterVertically,
         ) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
new file mode 100644
index 0000000..5f7bd47
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.button.ui.composable
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.Expandable
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
+import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
+import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
+import kotlinx.coroutines.flow.StateFlow
+
+/** [ComposeVolumePanelUiComponent] implementing a clickable button from a bottom row. */
+class ButtonComponent(
+    private val viewModelFlow: StateFlow<ButtonViewModel?>,
+    private val onClick: (Expandable) -> Unit
+) : ComposeVolumePanelUiComponent {
+
+    @Composable
+    override fun VolumePanelComposeScope.Content(modifier: Modifier) {
+        val viewModelByState by viewModelFlow.collectAsState()
+        val viewModel = viewModelByState ?: return
+
+        Column(
+            modifier = modifier,
+            verticalArrangement = Arrangement.spacedBy(12.dp),
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            Expandable(
+                modifier = Modifier.height(64.dp).fillMaxWidth(),
+                color = MaterialTheme.colorScheme.primaryContainer,
+                shape = RoundedCornerShape(28.dp),
+                contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+                borderStroke = BorderStroke(8.dp, MaterialTheme.colorScheme.surface),
+                onClick = onClick,
+            ) {
+                Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+                    Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+                }
+            }
+            Text(
+                text = viewModel.label.toString(),
+                style = MaterialTheme.typography.labelMedium,
+                maxLines = 1,
+                overflow = TextOverflow.Ellipsis,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 228111d..dfee684 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -22,6 +22,7 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.IconButtonDefaults
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedIconToggleButton
@@ -39,6 +40,7 @@
 import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
 import kotlinx.coroutines.flow.StateFlow
 
+/** [ComposeVolumePanelUiComponent] implementing a toggleable button from a bottom row. */
 class ToggleButtonComponent(
     private val viewModelFlow: StateFlow<ToggleButtonViewModel?>,
     private val onCheckedChange: (isChecked: Boolean) -> Unit
@@ -57,6 +59,7 @@
                 modifier = Modifier.height(64.dp).fillMaxWidth(),
                 checked = viewModel.isChecked,
                 onCheckedChange = onCheckedChange,
+                shape = RoundedCornerShape(28.dp),
                 colors =
                     IconButtonDefaults.outlinedIconToggleButtonColors(
                         containerColor = MaterialTheme.colorScheme.surface,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index 8ad6fdf..d49fed5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -167,6 +167,7 @@
             ) {
                 Icon(
                     icon = it.icon,
+                    tint = it.iconColor.toColor(),
                     modifier = Modifier.padding(12.dp).fillMaxSize(),
                 )
             }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
new file mode 100644
index 0000000..89251939
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.popup.ui.composable
+
+import android.view.Gravity
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformIconButton
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.statusbar.phone.create
+import javax.inject.Inject
+
+/** Volume panel bottom popup menu. */
+class VolumePanelPopup
+@Inject
+constructor(
+    private val dialogFactory: SystemUIDialogFactory,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
+) {
+
+    /**
+     * Shows a popup with the [expandable] animation.
+     *
+     * @param title is shown on the top of the popup
+     * @param content is the popup body
+     */
+    fun show(
+        expandable: Expandable,
+        title: @Composable (SystemUIDialog) -> Unit,
+        content: @Composable (SystemUIDialog) -> Unit,
+    ) {
+        val dialog =
+            dialogFactory.create(
+                theme = R.style.Theme_VolumePanelActivity_Popup,
+                dialogGravity = Gravity.BOTTOM,
+            ) {
+                PopupComposable(it, title, content)
+            }
+        val controller = expandable.dialogTransitionController()
+        if (controller == null) {
+            dialog.show()
+        } else {
+            dialogTransitionAnimator.show(dialog, controller)
+        }
+    }
+
+    @Composable
+    private fun PopupComposable(
+        dialog: SystemUIDialog,
+        title: @Composable (SystemUIDialog) -> Unit,
+        content: @Composable (SystemUIDialog) -> Unit,
+    ) {
+        Box(Modifier.fillMaxWidth()) {
+            Column(
+                modifier = Modifier.fillMaxWidth().padding(vertical = 20.dp),
+                verticalArrangement = Arrangement.spacedBy(20.dp),
+            ) {
+                Box(
+                    modifier =
+                        Modifier.padding(horizontal = 80.dp).fillMaxWidth().wrapContentHeight(),
+                    contentAlignment = Alignment.Center
+                ) {
+                    title(dialog)
+                }
+
+                Box(
+                    modifier =
+                        Modifier.padding(horizontal = 16.dp).fillMaxWidth().wrapContentHeight(),
+                    contentAlignment = Alignment.Center
+                ) {
+                    content(dialog)
+                }
+            }
+
+            PlatformIconButton(
+                modifier = Modifier.align(Alignment.TopEnd).size(64.dp).padding(20.dp),
+                iconResource = R.drawable.ic_close,
+                contentDescription = null,
+                onClick = { dialog.dismiss() },
+                colors =
+                    IconButtonDefaults.iconButtonColors(
+                        contentColor = MaterialTheme.colorScheme.outline
+                    )
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
index 86eb849..2285128 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -52,7 +52,7 @@
         if (layout.footerComponents.isNotEmpty()) {
             Row(
                 modifier = Modifier.fillMaxWidth().wrapContentHeight(),
-                horizontalArrangement = Arrangement.spacedBy(20.dp),
+                horizontalArrangement = Arrangement.spacedBy(if (isLargeScreen) 28.dp else 20.dp),
             ) {
                 for (component in layout.footerComponents) {
                     AnimatedVisibility(component.isVisible) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
index 10731c7..8df8d2e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
@@ -27,8 +27,8 @@
     val orientation: Int
         get() = state.orientation
 
-    /** Is true when Volume Panel is using wide-screen layout and false the otherwise. */
-    val isWideScreen: Boolean
+    /** Is true when Volume Panel is using large-screen layout and false the otherwise. */
+    val isLargeScreen: Boolean
         get() = state.isWideScreen
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index dd63420..8a1e6a8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -19,6 +19,7 @@
 import android.content.res.Configuration
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
@@ -26,6 +27,7 @@
 import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.navigationBarsPadding
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
@@ -43,6 +45,8 @@
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 
+private val padding = 24.dp
+
 @Composable
 fun VolumePanelRoot(
     viewModel: VolumePanelViewModel,
@@ -84,7 +88,18 @@
                     shape = RoundedCornerShape(topStart = radius, topEnd = radius),
                     color = MaterialTheme.colorScheme.surfaceContainer,
                 ) {
-                    Column { components?.let { componentsState -> Components(componentsState) } }
+                    components?.let { componentsState ->
+                        Components(
+                            componentsState,
+                            Modifier.padding(
+                                    start = padding,
+                                    top = padding,
+                                    end = padding,
+                                    bottom = 20.dp,
+                                )
+                                .navigationBarsPadding()
+                        )
+                    }
                 }
             }
         }
@@ -92,36 +107,35 @@
 }
 
 @Composable
-private fun VolumePanelComposeScope.Components(components: ComponentsLayout) {
-    if (orientation == Configuration.ORIENTATION_PORTRAIT) {
-        VerticalVolumePanelContent(
-            components,
-            modifier = Modifier.padding(24.dp),
-        )
-    } else {
-        HorizontalVolumePanelContent(
-            components,
-            modifier =
-                Modifier.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 20.dp)
-                    .heightIn(max = 236.dp),
-        )
+private fun VolumePanelComposeScope.Components(
+    layout: ComponentsLayout,
+    modifier: Modifier = Modifier
+) {
+    var columnModifier = modifier.widthIn(max = 800.dp)
+    if (!isLargeScreen && orientation != Configuration.ORIENTATION_PORTRAIT) {
+        columnModifier = columnModifier.heightIn(max = 332.dp)
     }
+    Column(modifier = columnModifier, verticalArrangement = Arrangement.spacedBy(padding)) {
+        if (orientation == Configuration.ORIENTATION_PORTRAIT || isLargeScreen) {
+            VerticalVolumePanelContent(layout)
+        } else {
+            HorizontalVolumePanelContent(layout)
+        }
+        BottomBar(layout = layout, modifier = Modifier)
+    }
+}
 
-    if (components.bottomBarComponent.isVisible) {
-        val horizontalPadding =
-            dimensionResource(R.dimen.volume_panel_bottom_bar_horizontal_padding)
+@Composable
+private fun VolumePanelComposeScope.BottomBar(
+    layout: ComponentsLayout,
+    modifier: Modifier = Modifier
+) {
+    if (layout.bottomBarComponent.isVisible) {
         Box(
-            modifier =
-                Modifier.fillMaxWidth()
-                    .navigationBarsPadding()
-                    .padding(
-                        start = horizontalPadding,
-                        end = horizontalPadding,
-                        bottom = dimensionResource(R.dimen.volume_panel_bottom_bar_bottom_padding),
-                    ),
+            modifier = modifier.fillMaxWidth(),
             contentAlignment = Alignment.Center,
         ) {
-            with(components.bottomBarComponent.component as ComposeVolumePanelUiComponent) {
+            with(layout.bottomBarComponent.component as ComposeVolumePanelUiComponent) {
                 Content(Modifier)
             }
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
new file mode 100644
index 0000000..e31cdcd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.data.repository
+
+import android.bluetooth.BluetoothDevice
+import android.net.Uri
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.settingslib.media.MediaDevice
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.localMediaRepository
+import com.android.systemui.volume.localMediaRepositoryFactory
+import com.android.systemui.volume.panel.component.anc.FakeSliceFactory
+import com.android.systemui.volume.panel.component.anc.sliceViewManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AncSliceRepositoryTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: AncSliceRepository
+
+    @Before
+    fun setup() {
+        with(kosmos) {
+            val slice = FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true)
+            whenever(sliceViewManager.bindSlice(any<Uri>())).thenReturn(slice)
+
+            underTest =
+                AncSliceRepositoryImpl(
+                    localMediaRepositoryFactory,
+                    testScope.testScheduler,
+                    sliceViewManager,
+                )
+        }
+    }
+
+    @Test
+    fun noConnectedDevice_noSlice() {
+        with(kosmos) {
+            testScope.runTest {
+                localMediaRepository.updateCurrentConnectedDevice(null)
+
+                val slice by collectLastValue(underTest.ancSlice(1))
+                runCurrent()
+
+                assertThat(slice).isNull()
+            }
+        }
+    }
+
+    @Test
+    fun connectedDevice_sliceReturned() {
+        with(kosmos) {
+            testScope.runTest {
+                localMediaRepository.updateCurrentConnectedDevice(createMediaDevice())
+
+                val slice by collectLastValue(underTest.ancSlice(1))
+                runCurrent()
+
+                assertThat(slice).isNotNull()
+            }
+        }
+    }
+
+    private fun createMediaDevice(sliceUri: String = "content://test.slice"): MediaDevice {
+        val bluetoothDevice: BluetoothDevice = mock {
+            whenever(getMetadata(any()))
+                .thenReturn(
+                    ("<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" +
+                            sliceUri +
+                            "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>")
+                        .toByteArray()
+                )
+        }
+        val cachedBluetoothDevice: CachedBluetoothDevice = mock {
+            whenever(device).thenReturn(bluetoothDevice)
+        }
+        return mock<BluetoothMediaDevice> {
+            whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt
new file mode 100644
index 0000000..553aed8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.domain
+
+import android.net.Uri
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.panel.component.anc.FakeSliceFactory
+import com.android.systemui.volume.panel.component.anc.ancSliceInteractor
+import com.android.systemui.volume.panel.component.anc.ancSliceRepository
+import com.android.systemui.volume.panel.component.anc.sliceViewManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AncAvailabilityCriteriaTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: AncAvailabilityCriteria
+
+    @Before
+    fun setup() {
+        with(kosmos) {
+            whenever(sliceViewManager.bindSlice(any<Uri>())).thenReturn(mock {})
+
+            underTest = AncAvailabilityCriteria(ancSliceInteractor)
+        }
+    }
+
+    @Test
+    fun noSlice_unavailable() {
+        with(kosmos) {
+            testScope.runTest {
+                ancSliceRepository.putSlice(1, null)
+
+                val isAvailable by collectLastValue(underTest.isAvailable())
+                runCurrent()
+
+                assertThat(isAvailable).isFalse()
+            }
+        }
+    }
+
+    @Test
+    fun hasSlice_available() {
+        with(kosmos) {
+            testScope.runTest {
+                ancSliceRepository.putSlice(
+                    1,
+                    FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true)
+                )
+
+                val isAvailable by collectLastValue(underTest.isAvailable())
+                runCurrent()
+
+                assertThat(isAvailable).isTrue()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt
new file mode 100644
index 0000000..53f0bc9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.panel.component.anc.FakeSliceFactory
+import com.android.systemui.volume.panel.component.anc.ancSliceRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AncSliceInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: AncSliceInteractor
+
+    @Before
+    fun setup() {
+        with(kosmos) {
+            underTest = AncSliceInteractor(ancSliceRepository, testScope.backgroundScope)
+        }
+    }
+
+    @Test
+    fun errorSlice_returnsNull() {
+        with(kosmos) {
+            testScope.runTest {
+                ancSliceRepository.putSlice(
+                    1,
+                    FakeSliceFactory.createSlice(hasError = true, hasSliceItem = true)
+                )
+
+                val slice by collectLastValue(underTest.ancSlice)
+                runCurrent()
+
+                assertThat(slice).isNull()
+            }
+        }
+    }
+
+    @Test
+    fun noSliceItem_returnsNull() {
+        with(kosmos) {
+            testScope.runTest {
+                ancSliceRepository.putSlice(
+                    1,
+                    FakeSliceFactory.createSlice(hasError = false, hasSliceItem = false)
+                )
+
+                val slice by collectLastValue(underTest.ancSlice)
+                runCurrent()
+
+                assertThat(slice).isNull()
+            }
+        }
+    }
+
+    @Test
+    fun sliceItem_noError_returnsSlice() {
+        with(kosmos) {
+            testScope.runTest {
+                ancSliceRepository.putSlice(
+                    1,
+                    FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true)
+                )
+
+                val slice by collectLastValue(underTest.ancSlice)
+                runCurrent()
+
+                assertThat(slice).isNotNull()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
index ec37925..ec55c75 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
@@ -17,8 +17,6 @@
 package com.android.systemui.volume.panel.component.mediaoutput.domain
 
 import android.media.AudioManager
-import android.media.session.MediaSession
-import android.media.session.PlaybackState
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -26,14 +24,8 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.volume.audioModeInteractor
 import com.android.systemui.volume.audioRepository
-import com.android.systemui.volume.localMediaRepository
-import com.android.systemui.volume.mediaController
-import com.android.systemui.volume.mediaControllerRepository
-import com.android.systemui.volume.mediaOutputInteractor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
@@ -54,23 +46,14 @@
 
     @Before
     fun setup() {
-        with(kosmos) {
-            whenever(mediaController.packageName).thenReturn("test.pkg")
-            whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
-            whenever(mediaController.playbackState).thenReturn(PlaybackState.Builder().build())
-
-            mediaControllerRepository.setActiveLocalMediaController(mediaController)
-
-            underTest = MediaOutputAvailabilityCriteria(mediaOutputInteractor, audioModeInteractor)
-        }
+        underTest = MediaOutputAvailabilityCriteria(kosmos.audioModeInteractor)
     }
 
     @Test
-    fun notInCallAndHasDevices_isAvailable_true() {
+    fun notInCall_isAvailable_true() {
         with(kosmos) {
             testScope.runTest {
                 audioRepository.setMode(AudioManager.MODE_NORMAL)
-                localMediaRepository.updateMediaDevices(listOf(mock {}))
 
                 val isAvailable by collectLastValue(underTest.isAvailable())
                 runCurrent()
@@ -79,27 +62,12 @@
             }
         }
     }
+
     @Test
-    fun inCallAndHasDevices_isAvailable_false() {
+    fun inCall_isAvailable_false() {
         with(kosmos) {
             testScope.runTest {
                 audioRepository.setMode(AudioManager.MODE_IN_CALL)
-                localMediaRepository.updateMediaDevices(listOf(mock {}))
-
-                val isAvailable by collectLastValue(underTest.isAvailable())
-                runCurrent()
-
-                assertThat(isAvailable).isFalse()
-            }
-        }
-    }
-
-    @Test
-    fun notInCallAndHasDevices_isAvailable_false() {
-        with(kosmos) {
-            testScope.runTest {
-                audioRepository.setMode(AudioManager.MODE_NORMAL)
-                localMediaRepository.updateMediaDevices(emptyList())
 
                 val isAvailable by collectLastValue(underTest.isAvailable())
                 runCurrent()
diff --git a/packages/SystemUI/res/drawable/ic_noise_aware.xml b/packages/SystemUI/res/drawable/ic_noise_aware.xml
new file mode 100644
index 0000000..5482641
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_noise_aware.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~  Licensed under the Apache License, Version 2.0 (the "License");
+  ~  you may not use this file except in compliance with the License.
+  ~  You may obtain a copy of the License at
+  ~
+  ~       http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~  Unless required by applicable law or agreed to in writing, software
+  ~  distributed under the License is distributed on an "AS IS" BASIS,
+  ~  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~  See the License for the specific language governing permissions and
+  ~  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M440,82Q450,81 460,80.5Q470,80 480,80Q491,80 500.5,80.5Q510,81 520,82L520,162Q510,160 500.5,160Q491,160 480,160Q469,160 459.5,160Q450,160 440,162L440,82ZM272,138Q289,127 306.5,119Q324,111 343,104L378,176Q358,182 340.5,190.5Q323,199 306,210L272,138ZM654,210Q637,199 619.5,190.5Q602,182 582,176L617,104Q636,111 653.5,119Q671,127 688,138L654,210ZM753,311Q742,294 729,278.5Q716,263 702,249L765,199Q779,213 792,228.5Q805,244 816,261L753,311ZM143,263Q154,246 166.5,230.5Q179,215 193,201L256,251Q242,265 229.5,280.5Q217,296 206,313L143,263ZM83,428Q85,408 90,388.5Q95,369 101,350L180,368Q173,387 168.5,406.5Q164,426 162,446L83,428ZM799,449Q797,429 792.5,409Q788,389 781,370L859,352Q865,371 870,390.5Q875,410 877,430L799,449ZM781,590Q788,571 792,552Q796,533 798,513L877,531Q875,551 870,570.5Q865,590 859,609L781,590ZM162,514Q164,534 168.5,553.5Q173,573 180,592L101,610Q95,591 90,571.5Q85,552 83,532L162,514ZM705,708Q719,694 731,678.5Q743,663 754,646L818,696Q807,713 794.5,728.5Q782,744 768,758L705,708ZM194,760Q180,746 167.5,730Q155,714 144,697L206,647Q217,664 229.5,680Q242,696 256,710L194,760ZM583,783Q603,776 620,768Q637,760 654,749L689,821Q672,832 654.5,840.5Q637,849 618,856L583,783ZM344,857Q325,850 307,841.5Q289,833 272,822L307,750Q324,761 341.5,769.5Q359,778 379,784L344,857ZM480,880Q470,880 460,879.5Q450,879 440,878L440,798Q453,800 480,800Q491,800 500.5,800Q510,800 520,798L520,878Q510,879 500.5,879.5Q491,880 480,880ZM520,720Q482,720 450.5,697Q419,674 406,638Q403,629 399.5,620.5Q396,612 389,605L334,550Q308,524 294,490.5Q280,457 280,420Q280,345 332.5,292.5Q385,240 460,240Q529,240 580,285.5Q631,331 639,400L558,400Q551,365 523.5,342.5Q496,320 460,320Q418,320 389,349Q360,378 360,420Q360,440 368,459.5Q376,479 391,494L445,548Q459,562 467.5,578.5Q476,595 482,612Q487,625 497,632.5Q507,640 520,640Q537,640 548.5,628.5Q560,617 560,600L640,600Q640,650 605.5,685Q571,720 520,720ZM540,560Q515,560 497.5,542.5Q480,525 480,500Q480,474 497.5,457Q515,440 540,440Q566,440 583,457Q600,474 600,500Q600,525 583,542.5Q566,560 540,560Z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/anc_slice.xml b/packages/SystemUI/res/layout/anc_slice.xml
new file mode 100644
index 0000000..71752f2
--- /dev/null
+++ b/packages/SystemUI/res/layout/anc_slice.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.
+  -->
+<androidx.slice.widget.SliceView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/slice_view"
+    style="@style/Widget.SliceView.Panel.Slider"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 65120a9..5436642 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -608,8 +608,6 @@
     <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
 
     <dimen name="volume_panel_corner_radius">52dp</dimen>
-    <dimen name="volume_panel_bottom_bar_horizontal_padding">24dp</dimen>
-    <dimen name="volume_panel_bottom_bar_bottom_padding">20dp</dimen>
 
     <!-- Size of each item in the ringer selector drawer. -->
     <dimen name="volume_ringer_drawer_item_size">42dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a7d93e7..495f20f2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1522,6 +1522,9 @@
     <string name="volume_stream_content_description_vibrate_a11y">%1$s. Tap to set to vibrate.</string>
     <string name="volume_stream_content_description_mute_a11y">%1$s. Tap to mute.</string>
 
+    <!-- Label for button to enabled/disable live caption [CHAR_LIMIT=30] -->
+    <string name="volume_panel_noise_control_title">Noise Control</string>
+
     <string name="volume_ringer_change">Tap to change ringer mode</string>
 
     <!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f1d4d71..617eadb 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -953,8 +953,11 @@
         <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
     </style>
 
-    <style name="Theme.VolumePanelActivity"
-        parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
+    <style name="Theme.VolumePanelActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
         <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowBackground">@android:color/transparent</item>
         <item name="android:backgroundDimEnabled">true</item>
@@ -962,6 +965,12 @@
         <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
     </style>
 
+    <style name="Theme.VolumePanelActivity.Popup" parent="@style/Theme.SystemUI.Dialog">
+        <item name="android:dialogCornerRadius">44dp</item>
+        <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainerHigh
+        </item>
+    </style>
+
     <style name="Theme.UserSwitcherFullscreenDialog" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
         <item name="android:statusBarColor">@color/user_switcher_fullscreen_bg</item>
         <item name="android:windowBackground">@color/user_switcher_fullscreen_bg</item>
diff --git a/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt b/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt
new file mode 100644
index 0000000..384acc4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.slice
+
+import android.net.Uri
+import androidx.slice.Slice
+import androidx.slice.SliceViewManager
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+
+/**
+ * Returns updating [Slice] for a [sliceUri]. It's null when there is no slice available for the
+ * provided Uri. This can change overtime because of external changes (like device being
+ * connected/disconnected).
+ */
+fun SliceViewManager.sliceForUri(sliceUri: Uri): Flow<Slice?> =
+    ConflatedCallbackFlow.conflatedCallbackFlow {
+        val callback = SliceViewManager.SliceCallback { launch { send(it) } }
+
+        val slice = bindSlice(sliceUri)
+        send(slice)
+        registerSliceCallback(sliceUri, callback)
+        awaitClose { unregisterSliceCallback(sliceUri, callback) }
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
index 4fe9c8c..f3a4f0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
@@ -24,6 +24,7 @@
 import androidx.activity.OnBackPressedDispatcher
 import androidx.activity.OnBackPressedDispatcherOwner
 import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
+import androidx.annotation.GravityInt
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
@@ -56,6 +57,7 @@
     sysUiState: SysUiState,
     broadcastDispatcher: BroadcastDispatcher,
     dialogTransitionAnimator: DialogTransitionAnimator,
+    @GravityInt private val dialogGravity: Int?,
 ) :
     SystemUIDialog(
         context,
@@ -90,6 +92,7 @@
     @CallSuper
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+        dialogGravity?.let { window?.setGravity(it) }
         onBackPressedDispatcher.setOnBackInvokedDispatcher(onBackInvokedDispatcher)
         savedStateRegistryController.performRestore(savedInstanceState)
         lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
index 553edf9b..1edd4d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone
 
 import android.content.Context
+import androidx.annotation.GravityInt
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Application
@@ -43,11 +44,14 @@
      * @param context the [Context] in which the dialog will be constructed.
      * @param dismissOnDeviceLock whether the dialog should be automatically dismissed when the
      *   device is locked (true by default).
+     * @param dialogGravity is one of the [android.view.Gravity] and determines dialog position on
+     *   the screen.
      */
     fun create(
         context: Context = this.applicationContext,
         theme: Int = SystemUIDialog.DEFAULT_THEME,
         dismissOnDeviceLock: Boolean = SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
+        @GravityInt dialogGravity: Int? = null,
     ): ComponentSystemUIDialog {
         Assert.isMainThread()
 
@@ -59,6 +63,7 @@
             sysUiState,
             broadcastDispatcher,
             dialogTransitionAnimator,
+            dialogGravity,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AncModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AncModule.kt
new file mode 100644
index 0000000..66df45c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AncModule.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.dagger
+
+import android.content.Context
+import androidx.slice.SliceViewManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.volume.panel.component.anc.data.repository.AncSliceRepository
+import com.android.systemui.volume.panel.component.anc.data.repository.AncSliceRepositoryImpl
+import dagger.Module
+import dagger.Provides
+
+/** Dagger module that provides ANC controlling backend. */
+@Module
+interface AncModule {
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        fun provideAncSliceRepository(
+            @Application context: Context,
+            implFactory: AncSliceRepositoryImpl.Factory
+        ): AncSliceRepository = implFactory.create(SliceViewManager.getInstance(context))
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 3285637..c6aee42 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -57,6 +57,7 @@
 @Module(
         includes = {
                 AudioModule.class,
+                AncModule.class,
                 CaptioningModule.class,
                 MediaDevicesModule.class
         },
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
new file mode 100644
index 0000000..8f18aa8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.data.repository
+
+import android.bluetooth.BluetoothDevice
+import android.net.Uri
+import androidx.slice.Slice
+import androidx.slice.SliceViewManager
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.slice.sliceForUri
+import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/** Provides ANC slice data */
+interface AncSliceRepository {
+
+    /**
+     * ANC slice with a given width. Emits null when there is no ANC slice available. This can mean
+     * that:
+     * - there is no supported device connected;
+     * - there is no slice provider for the uri;
+     */
+    fun ancSlice(width: Int): Flow<Slice?>
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class AncSliceRepositoryImpl
+@AssistedInject
+constructor(
+    mediaRepositoryFactory: LocalMediaRepositoryFactory,
+    @Background private val backgroundCoroutineContext: CoroutineContext,
+    @Assisted private val sliceViewManager: SliceViewManager,
+) : AncSliceRepository {
+
+    private val localMediaRepository = mediaRepositoryFactory.create(null)
+
+    override fun ancSlice(width: Int): Flow<Slice?> {
+        return localMediaRepository.currentConnectedDevice
+            .map { (it as? BluetoothMediaDevice)?.cachedDevice?.device?.getExtraControlUri(width) }
+            .distinctUntilChanged()
+            .flatMapLatest { sliceUri ->
+                sliceUri ?: return@flatMapLatest flowOf(null)
+                sliceViewManager.sliceForUri(sliceUri)
+            }
+            .flowOn(backgroundCoroutineContext)
+    }
+
+    private fun BluetoothDevice.getExtraControlUri(width: Int): Uri? {
+        val uri: String? = BluetoothUtils.getControlUriMetaData(this)
+        uri ?: return null
+
+        return if (uri.isEmpty()) {
+            null
+        } else {
+            Uri.parse(
+                "$uri$width" +
+                    "&version=${SliceParameters.VERSION}" +
+                    "&is_collapsed=${SliceParameters.IS_COLLAPSED}"
+            )
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(sliceViewManager: SliceViewManager): AncSliceRepositoryImpl
+    }
+
+    private object SliceParameters {
+        /**
+         * Slice version
+         * 1) legacy slice
+         * 2) new slice
+         */
+        const val VERSION = 2
+
+        /**
+         * Collapsed slice shows a single button, and expanded shows a row buttons. Supported since
+         * [VERSION]==2.
+         */
+        const val IS_COLLAPSED = false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt
new file mode 100644
index 0000000..89b9274
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.domain
+
+import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Determines if ANC component is available for the Volume Panel. */
+@VolumePanelScope
+class AncAvailabilityCriteria
+@Inject
+constructor(
+    private val ancSliceInteractor: AncSliceInteractor,
+) : ComponentAvailabilityCriteria {
+
+    override fun isAvailable(): Flow<Boolean> = ancSliceInteractor.ancSlice.map { it != null }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt
new file mode 100644
index 0000000..91af622
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.domain.interactor
+
+import android.app.slice.Slice.HINT_ERROR
+import android.app.slice.SliceItem.FORMAT_SLICE
+import androidx.slice.Slice
+import com.android.systemui.volume.panel.component.anc.data.repository.AncSliceRepository
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+
+/** Provides a valid slice from [AncSliceRepository]. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumePanelScope
+class AncSliceInteractor
+@Inject
+constructor(
+    private val ancSliceRepository: AncSliceRepository,
+    scope: CoroutineScope,
+) {
+
+    // Start with a positive width to check is the Slice is available.
+    private val width = MutableStateFlow(1)
+
+    /** Provides a valid ANC slice. */
+    val ancSlice: SharedFlow<Slice?> =
+        width
+            .flatMapLatest { width -> ancSliceRepository.ancSlice(width) }
+            .map { slice ->
+                if (slice?.isValidSlice() == true) {
+                    slice
+                } else {
+                    null
+                }
+            }
+            .shareIn(scope, SharingStarted.Eagerly, replay = 1)
+
+    /** Updates the width of the [ancSlice] */
+    fun changeWidth(newWidth: Int) {
+        width.value = newWidth
+    }
+
+    private fun Slice.isValidSlice(): Boolean {
+        if (hints.contains(HINT_ERROR)) {
+            return false
+        }
+        for (item in items) {
+            if (item.format == FORMAT_SLICE) {
+                return true
+            }
+        }
+        return false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
new file mode 100644
index 0000000..eb96f6c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
@@ -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.systemui.volume.panel.component.anc.ui.viewmodel
+
+import android.content.Context
+import androidx.slice.Slice
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Volume Panel ANC component view model. */
+@VolumePanelScope
+class AncViewModel
+@Inject
+constructor(
+    @Application private val context: Context,
+    @VolumePanelScope private val coroutineScope: CoroutineScope,
+    private val interactor: AncSliceInteractor,
+) {
+
+    /** ANC [Slice]. Null when there is no slice available for ANC. */
+    val slice: StateFlow<Slice?> =
+        interactor.ancSlice.stateIn(coroutineScope, SharingStarted.Eagerly, null)
+
+    /**
+     * ButtonViewModel to be shown in the VolumePanel. Null when there is no ANC Slice available.
+     */
+    val button: StateFlow<ButtonViewModel?> =
+        interactor.ancSlice
+            .map { slice ->
+                slice?.let {
+                    ButtonViewModel(
+                        Icon.Resource(R.drawable.ic_noise_aware, null),
+                        context.getString(R.string.volume_panel_noise_control_title)
+                    )
+                }
+            }
+            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+
+    /** Call this to update [slice] width in a reaction to container size change. */
+    fun changeSliceWidth(width: Int) {
+        interactor.changeWidth(width)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ButtonViewModel.kt
new file mode 100644
index 0000000..754d258
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ButtonViewModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.button.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+
+/** Models base buttons appearance. */
+data class ButtonViewModel(
+    val icon: Icon,
+    val label: CharSequence,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt
index 020ec64..bac7d15 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt
@@ -17,27 +17,19 @@
 package com.android.systemui.volume.panel.component.mediaoutput.domain
 
 import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
 
 /** Determines if the Media Output Volume Panel component is available. */
 @VolumePanelScope
 class MediaOutputAvailabilityCriteria
 @Inject
 constructor(
-    private val mediaOutputInteractor: MediaOutputInteractor,
     private val audioModeInteractor: AudioModeInteractor,
 ) : ComponentAvailabilityCriteria {
 
-    override fun isAvailable(): Flow<Boolean> {
-        return combine(mediaOutputInteractor.mediaDevices, audioModeInteractor.isOngoingCall) {
-            devices,
-            isOngoingCall ->
-            !isOngoingCall && devices.isNotEmpty()
-        }
-    }
+    override fun isAvailable(): Flow<Boolean> = audioModeInteractor.isOngoingCall.map { !it }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index 24cc29d..0f53437 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -95,10 +95,6 @@
     val currentConnectedDevice: Flow<MediaDevice?> =
         localMediaRepository.flatMapLatest { it.currentConnectedDevice }
 
-    /** A list of available [MediaDevice]s. */
-    val mediaDevices: Flow<Collection<MediaDevice>> =
-        localMediaRepository.flatMapLatest { it.mediaDevices }
-
     private suspend fun getApplicationLabel(packageName: String): CharSequence? {
         return try {
             withContext(backgroundCoroutineContext) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
index e0718ac..e518ed0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
@@ -23,15 +23,18 @@
 sealed interface DeviceIconViewModel {
 
     val icon: Icon
+    val iconColor: Color
     val backgroundColor: Color
 
     class IsPlaying(
         override val icon: Icon,
+        override val iconColor: Color,
         override val backgroundColor: Color,
     ) : DeviceIconViewModel
 
     class IsNotPlaying(
         override val icon: Icon,
+        override val iconColor: Color,
         override val backgroundColor: Color,
     ) : DeviceIconViewModel
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index d148992..85d6c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -86,13 +86,21 @@
                                 null
                             )
                     DeviceIconViewModel.IsPlaying(
-                        icon,
-                        Color.Attribute(com.android.internal.R.attr.materialColorSecondary),
+                        icon = icon,
+                        iconColor =
+                            Color.Attribute(com.android.internal.R.attr.materialColorSurface),
+                        backgroundColor =
+                            Color.Attribute(com.android.internal.R.attr.materialColorSecondary),
                     )
                 } else {
                     DeviceIconViewModel.IsNotPlaying(
-                        Icon.Resource(R.drawable.ic_media_home_devices, null),
-                        Color.Attribute(com.android.internal.R.attr.materialColorSurface),
+                        icon = Icon.Resource(R.drawable.ic_media_home_devices, null),
+                        iconColor =
+                            Color.Attribute(
+                                com.android.internal.R.attr.materialColorOnSurfaceVariant
+                            ),
+                        backgroundColor =
+                            Color.Attribute(com.android.internal.R.attr.materialColorSurface),
                     )
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
index 6c742ba..f11ac5e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
@@ -23,4 +23,5 @@
     const val MEDIA_OUTPUT: VolumePanelComponentKey = "media_output"
     const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
     const val CAPTIONING: VolumePanelComponentKey = "captioning"
+    const val ANC: VolumePanelComponentKey = "anc"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index afd3f61..df4972a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.volume.panel.dagger
 
+import com.android.systemui.volume.panel.component.anc.AncModule
 import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
 import com.android.systemui.volume.panel.component.captioning.CaptioningModule
 import com.android.systemui.volume.panel.component.mediaoutput.MediaOutputModule
@@ -46,6 +47,7 @@
             UiModule::class,
             // Components modules
             BottomBarModule::class,
+            AncModule::class,
             CaptioningModule::class,
             MediaOutputModule::class,
         ]
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
index 55d8de5..0d65c42 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
@@ -50,6 +50,7 @@
         @VolumePanelScope
         fun provideEnabledComponents(): Collection<VolumePanelComponentKey> {
             return setOf(
+                VolumePanelComponents.ANC,
                 VolumePanelComponents.CAPTIONING,
                 VolumePanelComponents.MEDIA_OUTPUT,
                 VolumePanelComponents.BOTTOM_BAR,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
index 867df4a..ec4da06 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
@@ -47,7 +47,8 @@
         @VolumePanelScope
         @FooterComponents
         fun provideFooterComponents(): Collection<VolumePanelComponentKey> {
-            return setOf(
+            return listOf(
+                VolumePanelComponents.ANC,
                 VolumePanelComponents.CAPTIONING,
             )
         }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/FakeSliceFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/FakeSliceFactory.kt
new file mode 100644
index 0000000..fc406ea
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/FakeSliceFactory.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc
+
+import androidx.slice.Slice
+import androidx.slice.SliceItem
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+object FakeSliceFactory {
+
+    fun createSlice(hasError: Boolean, hasSliceItem: Boolean): Slice {
+        return mock {
+            val sliceItem: SliceItem = mock {
+                whenever(format).thenReturn(android.app.slice.SliceItem.FORMAT_SLICE)
+            }
+
+            whenever(items)
+                .thenReturn(
+                    buildList {
+                        if (hasSliceItem) {
+                            add(sliceItem)
+                        }
+                    }
+                )
+
+            whenever(hints)
+                .thenReturn(
+                    buildList {
+                        if (hasError) {
+                            add(android.app.slice.Slice.HINT_ERROR)
+                        }
+                    }
+                )
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt
new file mode 100644
index 0000000..f9b7e69
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc
+
+import androidx.slice.SliceViewManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.volume.panel.component.anc.data.repository.FakeAncSliceRepository
+import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor
+
+var Kosmos.sliceViewManager: SliceViewManager by Kosmos.Fixture { mock {} }
+val Kosmos.ancSliceRepository by Kosmos.Fixture { FakeAncSliceRepository() }
+val Kosmos.ancSliceInteractor by
+    Kosmos.Fixture { AncSliceInteractor(ancSliceRepository, testScope.backgroundScope) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt
new file mode 100644
index 0000000..b66d7f9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.data.repository
+
+import androidx.slice.Slice
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeAncSliceRepository : AncSliceRepository {
+
+    private val sliceByWidth = mutableMapOf<Int, MutableStateFlow<Slice?>>()
+
+    override fun ancSlice(width: Int): Flow<Slice?> =
+        sliceByWidth.getOrPut(width) { MutableStateFlow(null) }
+
+    fun putSlice(width: Int, slice: Slice?) {
+        sliceByWidth.getOrPut(width) { MutableStateFlow(null) }.value = slice
+    }
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b505a7c..7349755 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -18,6 +18,7 @@
 
 import static android.content.Intent.ACTION_SCREEN_OFF;
 import static android.content.Intent.ACTION_SCREEN_ON;
+import static android.content.Intent.EXTRA_USER_ID;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
@@ -25,6 +26,8 @@
 import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
 import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
 
+import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID;
+import static com.android.internal.app.SetScreenLockDialogActivity.LAUNCH_REASON_DISABLE_QUIET_MODE;
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
 import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED;
@@ -137,6 +140,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.SetScreenLockDialogActivity;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.RoSystemProperties;
@@ -1676,6 +1680,10 @@
                 synchronized (mUsersLock) {
                     userInfo = getUserInfo(userId);
                 }
+                if (userInfo == null) {
+                    throw new IllegalArgumentException("Invalid user. Can't find user details "
+                            + "for userId " + userId);
+                }
                 if (!userInfo.isManagedProfile()) {
                     throw new IllegalArgumentException("Invalid flags: " + flags
                             + ". Can't skip credential check for the user");
@@ -1692,6 +1700,19 @@
                     if (onlyIfCredentialNotRequired) {
                         return false;
                     }
+
+                    if (android.multiuser.Flags.showSetScreenLockDialog()) {
+                        // Show the prompt to set a new screen lock if the device does not have one
+                        final KeyguardManager km = mContext.getSystemService(KeyguardManager.class);
+                        if (km != null && !km.isDeviceSecure()) {
+                            Intent setScreenLockPromptIntent =
+                                    SetScreenLockDialogActivity
+                                            .createBaseIntent(LAUNCH_REASON_DISABLE_QUIET_MODE);
+                            setScreenLockPromptIntent.putExtra(EXTRA_ORIGIN_USER_ID, userId);
+                            mContext.startActivity(setScreenLockPromptIntent);
+                            return false;
+                        }
+                    }
                     showConfirmCredentialToDisableQuietMode(userId, target, callingPackage);
                     return false;
                 }
@@ -1915,7 +1936,7 @@
         if (target != null) {
             callBackIntent.putExtra(Intent.EXTRA_INTENT, target);
         }
-        callBackIntent.putExtra(Intent.EXTRA_USER_ID, userId);
+        callBackIntent.putExtra(EXTRA_USER_ID, userId);
         callBackIntent.setPackage(mContext.getPackageName());
         callBackIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackage);
         callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index dfda1fc..3bc089f 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -662,7 +662,7 @@
             android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
     public void getEnhancedConfirmationTrustedInstallers_returnsTrustedInstallers()
             throws IOException {
-        String pkgName = "com.example.app";
+        String packageName = "com.example.app";
         String certificateDigestStr = "E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:"
                 + "8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0";
 
@@ -670,7 +670,7 @@
                 .toByteArray();
         String contents = "<config>"
                 + "<" + "enhanced-confirmation-trusted-installer" + " "
-                + "package=\"" + pkgName + "\""
+                + "package=\"" + packageName + "\""
                 + " sha256-cert-digest=\"" + certificateDigestStr + "\""
                 + "/>"
                 + "</config>";
@@ -684,10 +684,10 @@
 
         assertThat(actualTrustedInstallers.size()).isEqualTo(1);
         SignedPackage actual = actualTrustedInstallers.stream().findFirst().orElseThrow();
-        SignedPackage expected = new SignedPackage(pkgName, certificateDigest);
+        SignedPackage expected = new SignedPackage(packageName, certificateDigest);
 
         assertThat(actual.getCertificateDigest()).isEqualTo(expected.getCertificateDigest());
-        assertThat(actual.getPkgName()).isEqualTo(expected.getPkgName());
+        assertThat(actual.getPackageName()).isEqualTo(expected.getPackageName());
         assertThat(actual).isEqualTo(expected);
     }